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 index 96cbf60a1..99f04cbb2 100644 --- 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 @@ -28,15 +28,13 @@ import nodomain.freeyourgadget.gadgetbridge.util.CheckSums; public abstract class AbstractHuamiFirmwareInfo { private byte[] bytes; - private final int crc16; - private final int crc32; + private int crc16; + private int crc32; - protected final HuamiFirmwareType firmwareType; + protected HuamiFirmwareType firmwareType; public AbstractHuamiFirmwareInfo(byte[] bytes) { - this.bytes = bytes; - this.crc16 = CheckSums.getCRC16(bytes); - this.crc32 = CheckSums.getCRC32(bytes); + setBytes(bytes); this.firmwareType = determineFirmwareType(bytes); } @@ -70,6 +68,12 @@ public abstract class AbstractHuamiFirmwareInfo { 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 } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021FirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021FirmwareInfo.java index 014eea2ad..3fea24a83 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021FirmwareInfo.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/Huami2021FirmwareInfo.java @@ -17,6 +17,8 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.huami; +import org.json.JSONArray; +import org.json.JSONException; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -114,31 +116,96 @@ public abstract class Huami2021FirmwareInfo extends AbstractHuamiFirmwareInfo { } // Attempt to handle as an app / watchface - final JSONObject appJson = getAppJson(zipFile); - if (appJson == null) { - return HuamiFirmwareType.INVALID; + final JSONObject appJson = getJson(zipFile, "app.json"); + if (appJson != null) { + final String appType; + try { + appType = appJson.getJSONObject("app").getString("appType"); + } catch (final Exception e) { + LOG.error("Failed to get appType from app.json", e); + return HuamiFirmwareType.INVALID; + } + + switch (appType) { + case "watchface": + return HuamiFirmwareType.WATCHFACE; + case "app": + return HuamiFirmwareType.APP; + default: + LOG.warn("Unknown app type {}", appType); + } } - final String appType; - try { - appType = appJson.getJSONObject("app").getString("appType"); - } catch (final Exception e) { - LOG.error("Failed to get appType from app.json", e); - return HuamiFirmwareType.INVALID; - } - - switch (appType) { - case "watchface": - return HuamiFirmwareType.WATCHFACE; - case "app": - return HuamiFirmwareType.APP; - default: - LOG.warn("Unknown app type {}", appType); + // Attempt to handle as a zab file + final byte[] zpk = handleZabPackage(zipFile); + if (zpk != null) { + setBytes(zpk); + return handleZipPackage(zpk); } return HuamiFirmwareType.INVALID; } + /** + * A zab package is a zip file with: + * - manifest.json + * - .sc (source code) + * - One or more zpk files + *

+ * 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 (deviceName().equals(platform.getString("name"))) { + // It's compatible with the device, fetch device.zip + final String name = zpkEntry.getString("name"); + final byte[] zpkBytes = zipFile.getFileFromZip(name); + if (!ZipFile.isZipFile(zpkBytes)) { + LOG.warn("bytes for {} not a zip file", name); + continue; + } + final ZipFile zpkFile = new ZipFile(zpkBytes); + final byte[] deviceZip = zpkFile.getFileFromZip("device.zip"); + if (!ZipFile.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; + } + @Override public String toVersion(int crc16) { final String crcMapVersion = getCrcMap().get(crc16); @@ -261,7 +328,7 @@ public abstract class Huami2021FirmwareInfo extends AbstractHuamiFirmwareInfo { // TODO check i18n section? // TODO Show preview icon? - final JSONObject appJson = getAppJson(zipFile); + final JSONObject appJson = getJson(zipFile, "app.json"); if (appJson == null) { return null; } @@ -275,12 +342,12 @@ public abstract class Huami2021FirmwareInfo extends AbstractHuamiFirmwareInfo { return null; } - private static JSONObject getAppJson(final ZipFile zipFile) { + private static JSONObject getJson(final ZipFile zipFile, final String path) { final byte[] appJsonBin; try { - appJsonBin = zipFile.getFileFromZip("app.json"); + appJsonBin = zipFile.getFileFromZip(path); } catch (final ZipFileException e) { - LOG.error("Failed to read app.json", e); + LOG.error("Failed to read " + path, e); return null; } @@ -290,7 +357,7 @@ public abstract class Huami2021FirmwareInfo extends AbstractHuamiFirmwareInfo { .replace("\uFEFF", ""); return new JSONObject(appJsonString); } catch (final Exception e) { - LOG.error("Failed to parse app.json", e); + LOG.error("Failed to parse " + path, e); } return null;