From aeb8607e78c68b08eca065f14a76b9b334cace33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rebelo?= Date: Tue, 21 Mar 2023 00:02:59 +0000 Subject: [PATCH] Amazfit GTR 4 / GTS 4: Add watch Wi-Fi Hotspot and FTP Server --- .../DeviceSettingsPreferenceConst.java | 19 +++ .../devices/huami/Huami2021Coordinator.java | 14 ++ .../huami/Huami2021SettingsCustomizer.java | 38 ++++- .../amazfitgtr4/AmazfitGTR4Coordinator.java | 11 ++ .../amazfitgts4/AmazfitGTS4Coordinator.java | 11 ++ .../devices/huami/Huami2021Support.java | 52 +++++- .../services/ZeppOsFtpServerService.java | 156 ++++++++++++++++++ .../zeppos/services/ZeppOsWifiService.java | 141 ++++++++++++++++ app/src/main/res/drawable/ic_file_upload.xml | 10 ++ app/src/main/res/drawable/ic_person.xml | 10 ++ app/src/main/res/drawable/ic_play.xml | 10 ++ app/src/main/res/drawable/ic_stop.xml | 10 ++ app/src/main/res/drawable/ic_wifi.xml | 10 ++ .../main/res/drawable/ic_wifi_tethering.xml | 10 ++ app/src/main/res/values/strings.xml | 18 ++ .../res/xml/devicesettings_ftp_server.xml | 55 ++++++ .../res/xml/devicesettings_wifi_hotspot.xml | 47 ++++++ 17 files changed, 620 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsFtpServerService.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsWifiService.java create mode 100644 app/src/main/res/drawable/ic_file_upload.xml create mode 100644 app/src/main/res/drawable/ic_person.xml create mode 100644 app/src/main/res/drawable/ic_play.xml create mode 100644 app/src/main/res/drawable/ic_stop.xml create mode 100644 app/src/main/res/drawable/ic_wifi.xml create mode 100644 app/src/main/res/drawable/ic_wifi_tethering.xml create mode 100644 app/src/main/res/xml/devicesettings_ftp_server.xml create mode 100644 app/src/main/res/xml/devicesettings_wifi_hotspot.xml diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java index 75e13d84e..64a626070 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java @@ -24,6 +24,10 @@ public class DeviceSettingsPreferenceConst { 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_SCREEN_NIGHT_MODE = "pref_screen_night_mode"; public static final String PREF_SCREEN_SLEEP_MODE = "pref_screen_sleep_mode"; @@ -35,6 +39,8 @@ public class DeviceSettingsPreferenceConst { 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_LANGUAGE = "language"; public static final String PREF_LANGUAGE_AUTO = "auto"; @@ -228,6 +234,19 @@ public class DeviceSettingsPreferenceConst { 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 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 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"; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021Coordinator.java index 7de3abe75..0636e112b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021Coordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021Coordinator.java @@ -266,6 +266,12 @@ public abstract class Huami2021Coordinator extends HuamiCoordinator { // Developer // settings.add(R.xml.devicesettings_header_developer); + if (supportsWifiHotspot(device)) { + settings.add(R.xml.devicesettings_wifi_hotspot); + } + if (supportsFtpServer(device)) { + settings.add(R.xml.devicesettings_ftp_server); + } settings.add(R.xml.devicesettings_keep_activity_data_on_device); settings.add(R.xml.devicesettings_huami2021_fetch_operation_time_unit); @@ -337,6 +343,14 @@ public abstract class Huami2021Coordinator extends HuamiCoordinator { 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); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021SettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021SettingsCustomizer.java index 2eb079718..60e070d80 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021SettingsCustomizer.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/Huami2021SettingsCustomizer.java @@ -33,14 +33,15 @@ 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.DeviceSpecificSettingsHandler; import nodomain.freeyourgadget.gadgetbridge.capabilities.GpsCapability; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; -import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService; 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 Huami2021SettingsCustomizer extends HuamiSettingsCustomizer { @@ -215,6 +216,22 @@ public class Huami2021SettingsCustomizer extends HuamiSettingsCustomizer { )); setupGpsPreference(handler, prefs); + setupWifiFtpPreferences(handler); + } + + @Override + public Set getPreferenceKeysWithSummary() { + final Set 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) { @@ -319,6 +336,25 @@ public class Huami2021SettingsCustomizer extends HuamiSettingsCustomizer { } } + private void setupWifiFtpPreferences(final DeviceSpecificSettingsHandler handler) { + // Notify preference changed on button click, so we can react to them + final List wifiFtpButtons = Arrays.asList( + handler.findPreference(DeviceSettingsPreferenceConst.WIFI_HOTSPOT_START), + handler.findPreference(DeviceSettingsPreferenceConst.WIFI_HOTSPOT_STOP), + handler.findPreference(DeviceSettingsPreferenceConst.FTP_SERVER_START), + handler.findPreference(DeviceSettingsPreferenceConst.FTP_SERVER_STOP) + ); + + for (final Preference btn : wifiFtpButtons) { + if (btn != null) { + btn.setOnPreferenceClickListener(preference -> { + handler.notifyPreferenceChanged(btn.getKey()); + return true; + }); + } + } + } + /** * Removes all unsupported elements from a list preference. If they are not known, the preference * is hidden. 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 index cd96d5db0..0b1f0c699 100644 --- 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 @@ -29,6 +29,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.huami.Huami2021Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst; import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsAgpsInstallHandler; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate; import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; @@ -80,4 +81,14 @@ public class AmazfitGTR4Coordinator extends Huami2021Coordinator { public boolean supportsToDoList() { return true; } + + @Override + public boolean supportsWifiHotspot(final GBDevice device) { + return true; + } + + @Override + public boolean supportsFtpServer(final GBDevice device) { + return true; + } } 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 index e850a6b13..6e5ed0f02 100644 --- 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 @@ -29,6 +29,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.huami.Huami2021Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst; import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsAgpsInstallHandler; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate; import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; @@ -80,4 +81,14 @@ public class AmazfitGTS4Coordinator extends Huami2021Coordinator { public boolean supportsToDoList() { return true; } + + @Override + public boolean supportsWifiHotspot(final GBDevice device) { + return true; + } + + @Override + public boolean supportsFtpServer(final GBDevice device) { + return true; + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Support.java index 8d8a8018f..ddd47ccfa 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Support.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021Support.java @@ -77,6 +77,7 @@ import java.util.regex.Pattern; import nodomain.freeyourgadget.gadgetbridge.BuildConfig; import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone; @@ -116,11 +117,12 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.Hua import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation2021; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService; -import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.operations.ZeppOsAgpsFile; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.operations.ZeppOsAgpsUpdateOperation; 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.ZeppOsFileUploadService; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsFtpServerService; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsWifiService; import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils; import nodomain.freeyourgadget.gadgetbridge.util.BitmapUtil; import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; @@ -147,10 +149,14 @@ public abstract class Huami2021Support extends HuamiSupport { private final ZeppOsFileUploadService fileUploadService = new ZeppOsFileUploadService(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 Map mServiceMap = new HashMap() {{ put(fileUploadService.getEndpoint(), fileUploadService); put(configService.getEndpoint(), configService); put(agpsService.getEndpoint(), agpsService); + put(wifiService.getEndpoint(), wifiService); + put(ftpServerService.getEndpoint(), ftpServerService); }}; public Huami2021Support() { @@ -185,6 +191,43 @@ public abstract class Huami2021Support extends HuamiSupport { final ZeppOsConfigService.ConfigSetter configSetter = configService.newSetter(); final Prefs prefs = getDevicePrefs(); + // Handle button presses - these are not preferences + // See Huami2021SettingsCustomizer + switch (config) { + case DeviceSettingsPreferenceConst.WIFI_HOTSPOT_START: + final String ssid = getDevicePrefs().getString(DeviceSettingsPreferenceConst.WIFI_HOTSPOT_SSID, ""); + if (StringUtils.isNullOrEmpty(ssid)) { + LOG.error("Wi-Fi hotspot SSID not specified"); + return; + } + + final String password = getDevicePrefs().getString(DeviceSettingsPreferenceConst.WIFI_HOTSPOT_PASSWORD, ""); + if (StringUtils.isNullOrEmpty(password) || password.length() < 8) { + LOG.error("Wi-Fi hotspot password is not valid"); + return; + } + wifiService.startWifiHotspot(ssid, password); + return; + case DeviceSettingsPreferenceConst.WIFI_HOTSPOT_STOP: + wifiService.stopWifiHotspot(); + return; + case DeviceSettingsPreferenceConst.FTP_SERVER_START: + ftpServerService.startFtpServer(getDevicePrefs().getString(DeviceSettingsPreferenceConst.FTP_SERVER_ROOT_DIR, "")); + return; + case DeviceSettingsPreferenceConst.FTP_SERVER_STOP: + ftpServerService.stopFtpServer(); + return; + case DeviceSettingsPreferenceConst.WIFI_HOTSPOT_SSID: + case DeviceSettingsPreferenceConst.WIFI_HOTSPOT_PASSWORD: + case DeviceSettingsPreferenceConst.WIFI_HOTSPOT_STATUS: + 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; + } + try { if (configService.setConfig(prefs, config, configSetter)) { // If the ConfigSetter was able to set the config, just write it and return @@ -1384,6 +1427,13 @@ public abstract class Huami2021Support extends HuamiSupport { 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 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 000000000..a2648854a --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsFtpServerService.java @@ -0,0 +1,156 @@ +/* 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 . */ +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.devices.huami.Huami2021Support; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService; +import nodomain.freeyourgadget.gadgetbridge.util.GB; +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 Huami2021Support support) { + super(support); + } + + @Override + public short getEndpoint() { + return ENDPOINT; + } + + @Override + public boolean isEncrypted() { + return true; + } + + @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])); + } + } + + 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/ZeppOsWifiService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsWifiService.java new file mode 100644 index 000000000..6dc4a2b38 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsWifiService.java @@ -0,0 +1,141 @@ +/* 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 . */ +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.devices.huami.Huami2021Support; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService; +import nodomain.freeyourgadget.gadgetbridge.util.GB; + +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 Huami2021Support support) { + super(support); + } + + @Override + public short getEndpoint() { + return ENDPOINT; + } + + @Override + public boolean isEncrypted() { + return true; + } + + @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])); + } + } + + 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/res/drawable/ic_file_upload.xml b/app/src/main/res/drawable/ic_file_upload.xml new file mode 100644 index 000000000..4692e3877 --- /dev/null +++ b/app/src/main/res/drawable/ic_file_upload.xml @@ -0,0 +1,10 @@ + + + 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 000000000..cee7071bb --- /dev/null +++ b/app/src/main/res/drawable/ic_person.xml @@ -0,0 +1,10 @@ + + + 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 000000000..e623676fa --- /dev/null +++ b/app/src/main/res/drawable/ic_play.xml @@ -0,0 +1,10 @@ + + + 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 000000000..aac111d88 --- /dev/null +++ b/app/src/main/res/drawable/ic_stop.xml @@ -0,0 +1,10 @@ + + + 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 000000000..7fd8bdc82 --- /dev/null +++ b/app/src/main/res/drawable/ic_wifi.xml @@ -0,0 +1,10 @@ + + + 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 000000000..ab78ed029 --- /dev/null +++ b/app/src/main/res/drawable/ic_wifi_tethering.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fb01239d2..3f663cd3b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1046,6 +1046,8 @@ OK Dismiss Start + Stop + Status Set Export/Import directory content Show Export/Import directory content @@ -2010,4 +2012,20 @@ AsteroidOS SoFlow SO6 Lock + Wi-Fi Hotspot + Control the Wi-Fi hotspot on the watch + Wi-Fi Hotspot Configuration + SSID + Wi-Fi Hotspot Status + Start the Wi-Fi hotspot on the watch + Stop the Wi-Fi hotspot on the watch + FTP Server + Control the FTP server on the watch + Start the FTP server on the watch + Stop the FTP server on the watch + FTP Server Status + FTP Server Configuration + Root Directory + Address + Username 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 000000000..54d8e68b2 --- /dev/null +++ b/app/src/main/res/xml/devicesettings_ftp_server.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + 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 000000000..94c2f9258 --- /dev/null +++ b/app/src/main/res/xml/devicesettings_wifi_hotspot.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + +