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 index ef5b7fbd..e490e855 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandFWHelper.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandFWHelper.java @@ -3,6 +3,7 @@ package nodomain.freeyourgadget.gadgetbridge.devices.miband; import android.content.ContentResolver; import android.content.Context; import android.net.Uri; +import android.support.annotation.NonNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -10,27 +11,23 @@ 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.miband.Mi1SInfo; +import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.AbstractMiFirmwareInfo; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; /** - * Also see Mi1SInfo. + * Also see Mi1SFirmwareInfo. */ public class MiBandFWHelper { private static final Logger LOG = LoggerFactory.getLogger(MiBandFWHelper.class); - private static final int MI_FW_BASE_OFFSET = 1056; - private static final int MI1S_FW_BASE_OFFSET = 1092; private final Uri uri; private final ContentResolver cr; - private byte[] fw; - - private int baseOffset = -1; + private final @NonNull AbstractMiFirmwareInfo firmwareInfo; + private final @NonNull byte[] fw; /** * Provides a different notification API which is also used on Mi1A devices. @@ -56,7 +53,6 @@ public class MiBandFWHelper { throw new IOException("No content resolver"); } - baseOffset = determineBaseOffset(uri); String pebblePattern = ".*\\.(pbw|pbz|pbl)"; if (uri.getPath().matches(pebblePattern)) { @@ -65,62 +61,23 @@ public class MiBandFWHelper { try (InputStream in = new BufferedInputStream(cr.openInputStream(uri))) { this.fw = FileUtils.readAll(in, 1024 * 1024); // 1 MB - if (fw.length <= getOffsetFirmwareVersionMajor()) { - throw new IOException("This doesn't seem to be a Mi Band firmware, file size too small."); - } - byte firmwareVersionMajor = fw[getOffsetFirmwareVersionMajor()]; - if (!isSupportedFirmwareVersionMajor(firmwareVersionMajor)) { - throw new IOException("Firmware major version not supported, either too new or this isn't a Mi Band firmware: " + firmwareVersionMajor); - } + this.firmwareInfo = determineFirmwareInfoFor(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); } } - private int getOffsetFirmwareVersionMajor() { - return baseOffset + 3; - } - - private int getOffsetFirmwareVersionMinor() { - return baseOffset + 2; - } - - private int getOffsetFirmwareVersionRevision() { - return baseOffset + 1; - } - - private int getOffsetFirmwareVersionBuild() { - return baseOffset; - } - - private int determineBaseOffset(Uri uri) throws IOException { - String name = uri.getLastPathSegment().toLowerCase(); - if (name.startsWith("mili")) { - if (name.contains("_hr")) { - return MI1S_FW_BASE_OFFSET; - } - return MI_FW_BASE_OFFSET; - } else { - throw new IOException("Unknown file name " + name + "; cannot recognize firmware by it."); - } - } - - private byte getFirmwareVersionMajor() { - return fw[getOffsetFirmwareVersionMajor()]; - } - - private byte getFirmwareVersionMinor() { - return fw[getOffsetFirmwareVersionMinor()]; - } - - private boolean isSupportedFirmwareVersionMajor(byte firmwareVersionMajor) { - return firmwareVersionMajor == 1 || firmwareVersionMajor == 4 || firmwareVersionMajor == 5; - } - public int getFirmwareVersion() { - return (fw[getOffsetFirmwareVersionMajor()] << 24) | (fw[getOffsetFirmwareVersionMinor()] << 16) | (fw[getOffsetFirmwareVersionRevision()] << 8) | fw[getOffsetFirmwareVersionBuild()]; + // FIXME: UnsupportedOperationException! + return firmwareInfo.getFirst().getFirmwareVersion(); + } + + public int getFirmware2Version() { + return firmwareInfo.getFirst().getFirmwareVersion(); } public static String formatFirmwareVersion(int version) { @@ -135,11 +92,11 @@ public class MiBandFWHelper { } public String getHumanFirmwareVersion() { - return String.format(Locale.US, "%d.%d.%d.%d", fw[getOffsetFirmwareVersionMajor()], fw[getOffsetFirmwareVersionMinor()], fw[getOffsetFirmwareVersionRevision()], fw[getOffsetFirmwareVersionBuild()]); + return format(getFirmwareVersion()); } public String getHumanFirmwareVersion2() { - return format(Mi1SInfo.getFirmware2VersionFrom(getFw())); + return format(firmwareInfo.getSecond().getFirmwareVersion()); } public String format(int version) { @@ -160,20 +117,24 @@ public class MiBandFWHelper { } public boolean isFirmwareGenerallyCompatibleWith(GBDevice device) { - String deviceHW = device.getHardwareVersion(); - if (MiBandConst.MI_1.equals(deviceHW)) { - return getFirmwareVersionMajor() == 1; - } - if (MiBandConst.MI_1A.equals(deviceHW)) { - return getFirmwareVersionMajor() == 5; - } - if (MiBandConst.MI_1S.equals(deviceHW)) { - return getFirmwareVersionMajor() == 4; - } - return false; + return firmwareInfo.isGenerallyCompatibleWith(device); } public boolean isSingleFirmware() { - return Mi1SInfo.isSingleMiBandFirmware(getFw()); + return firmwareInfo.isSingleMiBandFirmware(); + } + + /** + * + * @param wholeFirmwareBytes + * @return + * @throws IllegalArgumentException when the data is not recognized as firmware data + */ + public static @NonNull AbstractMiFirmwareInfo determineFirmwareInfoFor(byte[] wholeFirmwareBytes) { + return AbstractMiFirmwareInfo.determineFirmwareInfoFor(wholeFirmwareBytes); + } + + public AbstractMiFirmwareInfo getFirmwareInfo() { + return firmwareInfo; } } 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 index e17f5c2e..5bb901b3 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/UserInfo.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/UserInfo.java @@ -85,7 +85,7 @@ public class UserInfo { sequence[8] = (byte) (type & 0xff); int aliasFrom = 9; - if (mDeviceInfo.isMili1A() || mDeviceInfo.isMilli1S()) { + if (mDeviceInfo.isMili1A() || mDeviceInfo.isMili1S()) { sequence[9] = (byte) (mDeviceInfo.feature & 255); sequence[10] = (byte) (mDeviceInfo.appearance & 255); aliasFrom = 11; 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 00000000..67247583 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/AbstractMi1FirmwareInfo.java @@ -0,0 +1,74 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.miband; + +import android.support.annotation.NonNull; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; + +/** + * 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 int MI1_FW_BASE_OFFSET = 1056; + + protected AbstractMi1FirmwareInfo(@NonNull byte[] wholeFirmwareBytes) { + super(wholeFirmwareBytes); + } + + @Override + public int getFirmwareOffset() + { + return 0; + } + + public int getFirmwareLength() + { + return wholeFirmwareBytes.length; + } + + 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() { + if (!isSingleMiBandFirmware()) { + return false; + } + try { + int majorVersion = getFirmwareVersionMajor(); + return majorVersion == getSupportedMajorVersion(); + } catch (IllegalArgumentException e) { + return false; + } catch (IndexOutOfBoundsException ex) { + return false; + } + } + + 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 00000000..e22bf9db --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/AbstractMi1SFirmwareInfo.java @@ -0,0 +1,18 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.miband; + +import android.support.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.getHardwareVersion()); + } +} 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 00000000..4f988517 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/AbstractMiFirmwareInfo.java @@ -0,0 +1,94 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.miband; + +import android.support.annotation.NonNull; + +import java.util.Arrays; + +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) { + 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(); + + protected abstract boolean isGenerallySupportedFirmware(); + + public abstract boolean isGenerallyCompatibleWith(GBDevice device); + + public @NonNull byte[] getFirmwareBytes() { + return Arrays.copyOfRange(wholeFirmwareBytes, getFirmwareOffset(), getFirmwareLength()); + } + + public int getFirmwareVersionMajor() { + int version = getFirmwareVersion(); + if (version > 0) { + return (version >> 24); + } + throw new IllegalArgumentException("bad firmware version: " + version); + } + + public boolean isSingleMiBandFirmware() { + // TODO: not sure if this is a correct check! + if ((wholeFirmwareBytes[7] & 255) != 1) { + return true; + } + return false; + } + + 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/DeviceInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/DeviceInfo.java index 74450139..2379fe8e 100644 --- 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 @@ -1,7 +1,6 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.miband; import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst; -import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandFWHelper; import nodomain.freeyourgadget.gadgetbridge.util.CheckSums; public class DeviceInfo extends AbstractInfo { @@ -76,7 +75,7 @@ public class DeviceInfo extends AbstractInfo { } public boolean supportsHeartrate() { - return isMilli1S(); + return isMili1S(); } @Override @@ -100,7 +99,7 @@ public class DeviceInfo extends AbstractInfo { return feature == 5 && appearance == 0 || feature == 0 && hwVersion == 208; } - public boolean isMilli1S() { + public boolean isMili1S() { // TODO: this is probably not quite correct, but hopefully sufficient for early 1S support return feature == 4 && appearance == 0 || feature == 4 && hwVersion == 4; } @@ -112,7 +111,7 @@ public class DeviceInfo extends AbstractInfo { if (isMili1A()) { return MiBandConst.MI_1A; } - if (isMilli1S()) { + if (isMili1S()) { return MiBandConst.MI_1S; } 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 00000000..ce975245 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1AFirmwareInfo.java @@ -0,0 +1,37 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.miband; + +import android.support.annotation.NonNull; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +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.getHardwareVersion(); + 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 00000000..aa506781 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1FirmwareInfo.java @@ -0,0 +1,37 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.miband; + +import android.support.annotation.NonNull; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +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.getHardwareVersion(); + 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 00000000..1447559e --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1SFirmwareInfo.java @@ -0,0 +1,69 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.miband; + +import android.support.annotation.Nullable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * FW1 is Mi Band firmware + * FW2 is heartrate firmware + */ +public class Mi1SFirmwareInfo extends AbstractMi1SFirmwareInfo { + private static final Logger LOG = LoggerFactory.getLogger(AbstractMi1FirmwareInfo.class); + + private final Mi1SFirmwareInfoFW1 fw1Info; + private final Mi1SFirmwareInfoFW2 fw2Info; + + private Mi1SFirmwareInfo(byte[] wholeFirmwareBytes) { + super(wholeFirmwareBytes); + fw1Info = new Mi1SFirmwareInfoFW1(wholeFirmwareBytes); + fw2Info = new Mi1SFirmwareInfoFW2(wholeFirmwareBytes); + } + + @Override + public AbstractMiFirmwareInfo getFirst() { + return fw1Info; + } + + @Override + public AbstractMiFirmwareInfo getSecond() { + return fw2Info; + } + + public static @Nullable Mi1SFirmwareInfo getInstance(byte[] wholeFirmwareBytes) { + Mi1SFirmwareInfo info = new Mi1SFirmwareInfo(wholeFirmwareBytes); + if (info.isGenerallySupportedFirmware()) { + return info; + } + LOG.info("firmware not supported"); + return null; + } + + @Override + protected boolean isGenerallySupportedFirmware() { + if (isSingleMiBandFirmware()) { + return false; + } + try { + return fw1Info.isGenerallySupportedFirmware() && fw2Info.isGenerallySupportedFirmware(); + } catch (IndexOutOfBoundsException 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/Mi1SFirmwareInfoFW1.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1SFirmwareInfoFW1.java new file mode 100644 index 00000000..3270f0d8 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1SFirmwareInfoFW1.java @@ -0,0 +1,53 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.miband; + +import android.support.annotation.NonNull; + +/** + * FW1 is Mi Band firmware + * FW2 is heartrate firmware + */ +public class Mi1SFirmwareInfoFW1 extends AbstractMi1SFirmwareInfo { + + private static final int MI1S_FW_BASE_OFFSET = 1092; + + Mi1SFirmwareInfoFW1(@NonNull byte[] wholeFirmwareBytes) { + super(wholeFirmwareBytes); + } + + @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(); + return majorVersion == 4; + } catch (IllegalArgumentException e) { + 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 00000000..86d4d61e --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1SFirmwareInfoFW2.java @@ -0,0 +1,51 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.miband; + +import android.support.annotation.NonNull; + +/** + * FW1 is Mi Band firmware + * FW2 is heartrate firmware + */ +public class Mi1SFirmwareInfoFW2 extends AbstractMi1SFirmwareInfo { + + Mi1SFirmwareInfoFW2(@NonNull byte[] wholeFirmwareBytes) { + super(wholeFirmwareBytes); + } + + @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(); + return majorVersion == 1; + } catch (IllegalArgumentException e) { + 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/Mi1SInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1SInfo.java deleted file mode 100644 index 7a1448e1..00000000 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/Mi1SInfo.java +++ /dev/null @@ -1,68 +0,0 @@ -package nodomain.freeyourgadget.gadgetbridge.service.devices.miband; - -import android.support.annotation.NonNull; - -/** - * FW1 is Mi Band firmware - * FW2 is heartrate firmware - */ -public class Mi1SInfo { - - public static int getFirmware2OffsetIn(@NonNull byte[] wholeFirmwareBytes) - { - return (wholeFirmwareBytes[26] & 255) << 24 - | (wholeFirmwareBytes[27] & 255) << 16 - | (wholeFirmwareBytes[28] & 255) << 8 - | (wholeFirmwareBytes[29] & 255); - } - - public static int getFirmware2LengthIn(@NonNull byte[] wholeFirmwareBytes) - { - return (wholeFirmwareBytes[30] & 255) << 24 - | (wholeFirmwareBytes[31] & 255) << 16 - | (wholeFirmwareBytes[32] & 255) << 8 - | (wholeFirmwareBytes[33] & 255); - } - - public static int getFirmware1OffsetIn(@NonNull byte[] wholeFirmwareBytes) - { - return (wholeFirmwareBytes[12] & 255) << 24 - | (wholeFirmwareBytes[13] & 255) << 16 - | (wholeFirmwareBytes[14] & 255) << 8 - | (wholeFirmwareBytes[15] & 255); - } - - public static int getFirmware1LengthIn(@NonNull byte[] wholeFirmwareBytes) - { - return (wholeFirmwareBytes[16] & 255) << 24 - | (wholeFirmwareBytes[17] & 255) << 16 - | (wholeFirmwareBytes[18] & 255) << 8 - | (wholeFirmwareBytes[19] & 255); - } - - public static int getFirmware1VersionFrom(@NonNull byte[] wholeFirmwareBytes) - { - return (wholeFirmwareBytes[8] & 255) << 24 - | (wholeFirmwareBytes[9] & 255) << 16 - | (wholeFirmwareBytes[10] & 255) << 8 - | wholeFirmwareBytes[11] & 255; - } - - public static int getFirmware2VersionFrom(@NonNull byte[] wholeFirmwareBytes) - { - return (wholeFirmwareBytes[22] & 255) << 24 - | (wholeFirmwareBytes[23] & 255) << 16 - | (wholeFirmwareBytes[24] & 255) << 8 - | wholeFirmwareBytes[25] & 255; - } - - // FIXME: this method is wrong. We don't know a way to check if a firmware file - // contains one or more firmwares. - public static boolean isSingleMiBandFirmware(@NonNull byte[] wholeFirmwareBytes) { - if ((wholeFirmwareBytes[7] & 255) != 1) { - return true; - } - return false; - } - -} 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 index 68da0b40..9f98c896 100644 --- 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 @@ -18,7 +18,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService; 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.miband.Mi1SInfo; +import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.AbstractMiFirmwareInfo; import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport; import nodomain.freeyourgadget.gadgetbridge.util.CheckSums; import nodomain.freeyourgadget.gadgetbridge.util.GB; @@ -28,8 +28,6 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation { private final Uri uri; private boolean firmwareInfoSent = false; -// private byte[] newFirmware; -// private boolean rebootWhenBandReady = false; private UpdateCoordinator updateCoordinator; public UpdateFirmwareOperation(Uri uri, MiBandSupport support) { @@ -41,11 +39,16 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation { protected void doPerform() throws IOException { MiBandFWHelper mFwHelper = new MiBandFWHelper(uri, getContext()); -// if (getSupport().supportsHeartRate()) { - updateCoordinator = prepareFirmwareInfo1S(mFwHelper.getFw()); -// } else { -// updateCoordinator = sendFirmwareInfo(mFwHelper.getFw(), mFwHelper.getFirmwareVersion()); -// } + 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(); // updateCoordinator.initNextOperation(); // FIXME: remove, just testing mi band 1s fw update @@ -75,7 +78,7 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation { } /** - * React to unsolicited messages sent by the Mi Band to the MiBandService.UUID_CHARACTERISTIC_NOTIFICATION + * 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. @@ -97,25 +100,19 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation { switch (value[0]) { case MiBandService.NOTIFY_FW_CHECK_SUCCESS: -// if (firmwareInfoSent && newFirmware != null) { if (firmwareInfoSent) { GB.toast(getContext(), "Firmware metadata successfully sent.", Toast.LENGTH_LONG, GB.INFO); - if (updateCoordinator.sendFwData()) { -// if (sendFirmwareData(newFirmware)) { -// rebootWhenBandReady = true; // disabled for testing - } else { + if (!updateCoordinator.sendFwData()) { //TODO: the firmware transfer failed, but the miband should be still functional with the old firmware. What should we do? GB.toast(getContext().getString(R.string.updatefirmwareoperation_updateproblem_do_not_reboot), Toast.LENGTH_LONG, GB.ERROR); done(); } firmwareInfoSent = false; -// newFirmware = null; } break; case MiBandService.NOTIFY_FW_CHECK_FAILED: GB.toast(getContext().getString(R.string.updatefirmwareoperation_metadata_updateproblem), Toast.LENGTH_LONG, GB.ERROR); firmwareInfoSent = false; -// newFirmware = null; done(); break; case MiBandService.NOTIFY_FIRMWARE_UPDATE_SUCCESS: @@ -130,7 +127,6 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation { GB.toast(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().onReboot(); -// rebootWhenBandReady = false; } else { LOG.error("BUG: Successful firmware update without reboot???"); } @@ -140,7 +136,6 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation { //TODO: the firmware transfer failed, but the miband should be still functional with the old firmware. What should we do? GB.toast(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()); -// rebootWhenBandReady = false; done(); break; @@ -150,63 +145,41 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation { } } -// /** -// * 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. -// *

-// * The Mi Band will send a notification after receiving these data to confirm if the metadata looks good to it. -// * -// * @param fwBytes -// * @param newFwVersion -// * @see MiBandSupport#handleNotificationNotif -// */ -// private byte[] sendFirmwareInfo(int currentFwVersion, int newFwVersion, int newFwSize, int checksum) throws IOException { -// private UpdateCoordinator sendFirmwareInfo(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 = 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) (checksum >> 8), -//// (byte) 0 // TEST, only for Mi1S! -// }; -// return new SingleUpdateCoordinator(fwInfo, fwBytes); -// } + /** + * 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. + *

+ * The Mi Band will send a notification after receiving these data to confirm if the metadata looks good to it. + * + * @param newFwVersion + * @see MiBandSupport#handleNotificationNotif + */ + 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); - private UpdateCoordinator prepareFirmwareInfo1S(byte[] wholeFirmwareBytes) { - int fw2Version = Mi1SInfo.getFirmware2VersionFrom(wholeFirmwareBytes); - int fw2Offset = Mi1SInfo.getFirmware2OffsetIn(wholeFirmwareBytes); - int fw2Length = Mi1SInfo.getFirmware2LengthIn(wholeFirmwareBytes); + byte[] fwInfo = prepareFirmwareUpdateA(currentFwVersion, newFwVersion, newFwSize, checksum); + return new SingleUpdateCoordinator(fwInfo, fwBytes, true); + } - int fw1Version = Mi1SInfo.getFirmware1VersionFrom(wholeFirmwareBytes); - int fw1Offset = Mi1SInfo.getFirmware1OffsetIn(wholeFirmwareBytes); - int fw1Length = Mi1SInfo.getFirmware1LengthIn(wholeFirmwareBytes); + 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 = new byte[fw2Length]; - System.arraycopy(wholeFirmwareBytes, fw2Offset, fw2Bytes, 0, fw2Length); + byte[] fw2Bytes = info.getSecond().getFirmwareBytes(); int fw2Checksum = CheckSums.getCRC16(fw2Bytes) ^ encodedMac; - byte[] fw1Bytes = new byte[fw1Length]; - System.arraycopy(wholeFirmwareBytes, fw1Offset, fw1Bytes, 0, fw1Length); + byte[] fw1Bytes = info.getFirst().getFirmwareBytes(); int fw1Checksum = encodedMac ^ CheckSums.getCRC16(fw1Bytes); // check firmware validity? @@ -215,22 +188,18 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation { int fw2OldVersion = getSupport().getDeviceInfo().getHeartrateFirmwareVersion(); boolean rebootWhenFinished = true; - if (Mi1SInfo.isSingleMiBandFirmware(wholeFirmwareBytes)) { + 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) now"); + 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, 1, rebootWhenFinished /*, progress monitor */); return new DoubleUpdateCoordinator(fw1Info, fw1Bytes, fw2Info, fw2Bytes, rebootWhenFinished); } } -// private Transaction createUpdateFirmwareTransaction() { -// -// } - private byte[] prepareFirmwareInfo(byte[] fwBytes, int currentFwVersion, int newFwVersion, int checksum, int something, boolean reboot) { byte[] fwInfo; switch (something) { @@ -262,12 +231,6 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation { (byte) checksum, (byte) (checksum >> 8) }; -// 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; } @@ -286,21 +249,8 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation { (byte) (newFwSize >> 8), (byte) checksum, (byte) (checksum >> 8), - (byte) something // 0 TEST, only for Mi1S! + (byte) something }; - -// // send to CONTROL POINT: -// if (!this.b(CONTROL_POINT, new byte[]{7, -// (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 false; -// } -// // wait for bq != -1 -// if (bq == 12) { -// return true; -// } return fwInfo; } @@ -388,6 +338,9 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation { } public boolean sendFwData() { +// if (true) { +// return true; // FIXME: temporarily disabled firmware sending +// } return sendFirmwareData(getFirmwareBytes()); } 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 index b842ec1e..aeee2b3b 100644 --- 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 @@ -6,6 +6,7 @@ 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; @@ -18,19 +19,19 @@ public class FirmwareTest { 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; + @Test - public void testFirmwareMi() throws Exception{ + public void testFirmwareMi1() throws Exception{ byte[] wholeFw = getFirmwareMi(); Assert.assertNotNull(wholeFw); - Assert.assertTrue(Mi1SInfo.isSingleMiBandFirmware(wholeFw)); - int calculatedLength = Mi1SInfo.getFirmware1LengthIn(wholeFw); - Assert.assertTrue("Unexpected firmware length: " + wholeFw.length, calculatedLength < wholeFw.length); - int calculatedVersion = Mi1SInfo.getFirmware1VersionFrom(wholeFw); -// Assert.assertEquals("Unexpected firmware version: " + calculatedVersion, MI_FW_VERSION, calculatedVersion); - + AbstractMiFirmwareInfo info = getFirmwareInfo(wholeFw, SINGLE); + int calculatedVersion = info.getFirmwareVersion(); String version = MiBandFWHelper.formatFirmwareVersion(calculatedVersion); Assert.assertTrue(version.startsWith("1.")); +// Assert.assertEquals("Unexpected firmware version: " + calculatedVersion, MI_FW_VERSION, calculatedVersion); } @Test @@ -38,14 +39,11 @@ public class FirmwareTest { byte[] wholeFw = getFirmwareMi1A(); Assert.assertNotNull(wholeFw); - Assert.assertTrue(Mi1SInfo.isSingleMiBandFirmware(wholeFw)); - int calculatedLength = Mi1SInfo.getFirmware1LengthIn(wholeFw); - Assert.assertTrue("Unexpected firmware length: " + wholeFw.length, calculatedLength < wholeFw.length); - int calculatedVersion = Mi1SInfo.getFirmware1VersionFrom(wholeFw); -// Assert.assertEquals("Unexpected firmware version: " + calculatedVersion, MI1A_FW_VERSION, calculatedVersion); - + AbstractMiFirmwareInfo info = getFirmwareInfo(wholeFw, SINGLE); + int calculatedVersion = info.getFirmwareVersion(); String version = MiBandFWHelper.formatFirmwareVersion(calculatedVersion); Assert.assertTrue(version.startsWith("5.")); +// Assert.assertEquals("Unexpected firmware version: " + calculatedVersion, MI1A_FW_VERSION, calculatedVersion); } @Test @@ -53,31 +51,71 @@ public class FirmwareTest { byte[] wholeFw = getFirmwareMi1S(); Assert.assertNotNull(wholeFw); - Assert.assertFalse(Mi1SInfo.isSingleMiBandFirmware(wholeFw)); + AbstractMiFirmwareInfo info = getFirmwareInfo(wholeFw, DOUBLE); // Mi Band version - int calculatedLengthFw1 = Mi1SInfo.getFirmware1LengthIn(wholeFw); - int calculatedOffsetFw1 = Mi1SInfo.getFirmware1OffsetIn(wholeFw); + int calculatedLengthFw1 = info.getFirst().getFirmwareLength(); + int calculatedOffsetFw1 = info.getFirst().getFirmwareOffset(); int endIndexFw1 = calculatedOffsetFw1 + calculatedLengthFw1; - int calculatedLengthFw2 = Mi1SInfo.getFirmware2LengthIn(wholeFw); - int calculatedOffsetFw2 = Mi1SInfo.getFirmware2OffsetIn(wholeFw); + int calculatedLengthFw2 = info.getSecond().getFirmwareLength(); + int calculatedOffsetFw2 = info.getSecond().getFirmwareOffset(); int endIndexFw2 = calculatedOffsetFw2 + calculatedLengthFw2; Assert.assertTrue(endIndexFw1 <= wholeFw.length - calculatedLengthFw2); Assert.assertTrue(endIndexFw2 <= wholeFw.length); Assert.assertTrue(endIndexFw1 <= calculatedOffsetFw2); - int calculatedVersionFw1 = Mi1SInfo.getFirmware1VersionFrom(wholeFw); + 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 = Mi1SInfo.getFirmware2VersionFrom(wholeFw); + 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.assertNotEquals(info.getFirst().getFirmwareOffset(), info.getSecond().getFirmwareOffset()); + Assert.assertFalse(Arrays.equals(info.getFirst().getFirmwareBytes(), info.getSecond().getFirmwareBytes())); + } + + private AbstractMiFirmwareInfo getFirmwareInfo(byte[] wholeFw, int numFirmwares) { + AbstractMiFirmwareInfo info = AbstractMiFirmwareInfo.determineFirmwareInfoFor(wholeFw); + 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 File getFirmwareDir() {