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 45ab910be..49ae67868 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 @@ -83,6 +83,11 @@ public abstract class Huami2021Coordinator extends HuamiCoordinator { return handler.isValid() ? handler : null; } + @Override + public boolean supportsScreenshots() { + return true; + } + @Override public boolean supportsHeartRateMeasurement(final GBDevice device) { return true; 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 index 97850b316..057b4f9d6 100644 --- 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 @@ -61,6 +61,11 @@ public class MiBand7Coordinator extends Huami2021Coordinator { return new MiBand7FWInstallHandler(uri, context); } + @Override + public boolean supportsScreenshots() { + return false; + } + @Override public boolean supportsBluetoothPhoneCalls(final GBDevice device) { return false; 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 28ec53d38..f6565ad98 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 @@ -48,6 +48,8 @@ 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.net.MalformedURLException; import java.net.URL; @@ -74,6 +76,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventScreenshot; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.huami.Huami2021Coordinator; @@ -125,6 +128,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.service 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.DeviceHelper; +import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs; import nodomain.freeyourgadget.gadgetbridge.util.MapUtils; @@ -305,6 +309,11 @@ public abstract class Huami2021Support extends HuamiSupport implements ZeppOsFil } } + @Override + public void onScreenshotReq() { + appsService.requestScreenshot(); + } + @Override public void onSetHeartRateMeasurementInterval(final int seconds) { try { @@ -1883,6 +1892,32 @@ public abstract class Huami2021Support extends HuamiSupport implements ZeppOsFil @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); + 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); + targetFolder.mkdirs(); + outputStream.write(data); + } catch (final IOException e) { + LOG.error("Failed to save file bytes", e); + } } private byte bool(final boolean b) { 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 index efd969699..33fd511c2 100644 --- 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 @@ -29,23 +29,27 @@ import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo; import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp; +import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions; 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 ZeppOsAppsService extends AbstractZeppOsService { private static final Logger LOG = LoggerFactory.getLogger(ZeppOsAppsService.class); private static final short ENDPOINT = 0x00a0; - private static final byte CMD_BYTE = 0x02; + 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_APP_LIST = 0x01; - private static final byte CMD_APP_DELETE = 0x03; - private static final byte CMD_APP_DELETING = 0x04; + 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; public ZeppOsAppsService(final Huami2021Support support) { super(support); @@ -63,28 +67,62 @@ public class ZeppOsAppsService extends AbstractZeppOsService { @Override public void handlePayload(final byte[] payload) { - if (payload[0] != CMD_BYTE) { - LOG.warn("Unexpected apps byte {}", String.format("0x%02x", payload[0])); - return; + 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])); } + } + 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 apps 2nd byte {}", String.format("0x%02x", payload[1])); + LOG.warn("Unexpected non-incoming payload ({})", String.format("0x%02x", payload[1])); return; } switch (payload[2]) { - case CMD_APP_LIST: + case CMD_APPS_LIST: parseAppList(payload); return; - case CMD_APP_DELETE: + case CMD_APPS_DELETE: LOG.info("Got app delete"); return; - case CMD_APP_DELETING: + 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 {}", GB.hexdump(payload)); + 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]); } } @@ -130,22 +168,34 @@ public class ZeppOsAppsService extends AbstractZeppOsService { final ByteBuffer buf = ByteBuffer.allocate(16).order(ByteOrder.LITTLE_ENDIAN); - buf.put(CMD_BYTE); + buf.put(CMD_APPS); buf.put(CMD_OUTGOING); - buf.put(CMD_APP_LIST); + buf.put(CMD_APPS_LIST); buf.put((byte) 0x00); write("request apps", 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 int appId) { LOG.info("Delete app {}", String.format("0x%08x", appId)); final ByteBuffer buf = ByteBuffer.allocate(20).order(ByteOrder.LITTLE_ENDIAN); - buf.put(CMD_BYTE); + buf.put(CMD_APPS); buf.put(CMD_OUTGOING); - buf.put(CMD_APP_DELETE); + buf.put(CMD_APPS_DELETE); buf.put((byte) 0x00); buf.putInt(0x00); buf.putInt(0x00); @@ -154,4 +204,17 @@ public class ZeppOsAppsService extends AbstractZeppOsService { 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()); + } }