diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6117c508..2303ffda 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -89,6 +89,26 @@ + + + + + + + + + + + + + + + + + + + + @@ -142,6 +162,26 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/FirmwareType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/FirmwareType.java new file mode 100644 index 00000000..4b36d21f --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/FirmwareType.java @@ -0,0 +1,19 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.miband2; + +public enum FirmwareType { + FIRMWARE((byte) 0), + FONT((byte) 1), + UNKNOWN1((byte) 2), + UNKNOWN2((byte) 3), + INVALID(Byte.MIN_VALUE); + + private final byte value; + + FirmwareType(byte value) { + this.value = value; + } + + public byte getValue() { + return value; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/Mi2FirmwareInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/Mi2FirmwareInfo.java index 4dbf595e..08b2e35b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/Mi2FirmwareInfo.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/Mi2FirmwareInfo.java @@ -27,13 +27,23 @@ public class Mi2FirmwareInfo { (byte) 0xf3, (byte) 0xe7, }; + private static final int FW_HEADER_OFFSET = 0x150; + private static final byte[] FT_HEADER = new byte[] { // HMZK font file (*.ft, *.ft.xx) + 0x48, + 0x4d, + 0x5a, + 0x4b + }; + private static Map crcToVersion = new HashMap<>(); static { crcToVersion.put(41899, "1.0.0.39"); } + private FirmwareType firmwareType = FirmwareType.FIRMWARE; + public static String toVersion(int crc16) { return crcToVersion.get(crc16); } @@ -51,6 +61,18 @@ public class Mi2FirmwareInfo { this.bytes = bytes; crc16 = CheckSums.getCRC16(bytes); firmwareVersion = crcToVersion.get(crc16); + firmwareType = determineFirmwareType(bytes); + } + + private FirmwareType determineFirmwareType(byte[] bytes) { + if (ArrayUtils.startsWith(bytes, FT_HEADER)) { + return FirmwareType.FONT; + } + if (ArrayUtils.equals(bytes, FW_HEADER, FW_HEADER_OFFSET)) { + // TODO: this is certainly not a correct validation, but it works for now + return FirmwareType.FIRMWARE; + } + return FirmwareType.INVALID; } public boolean isGenerallyCompatibleWith(GBDevice device) { @@ -58,8 +80,7 @@ public class Mi2FirmwareInfo { } public boolean isHeaderValid() { - // TODO: this is certainly not a correct validation, but it works for now - return ArrayUtils.equals(bytes, FW_HEADER, FW_HEADER_OFFSET); + return getFirmwareType() != FirmwareType.INVALID; } public void checkValid() throws IllegalArgumentException { @@ -84,4 +105,8 @@ public class Mi2FirmwareInfo { public int getFirmwareVersion() { return getCrc16(); // HACK until we know how to determine the version from the fw bytes } + + public FirmwareType getFirmwareType() { + return firmwareType; + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/UpdateFirmwareOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/UpdateFirmwareOperation.java index dfb2d0cd..dc5fa9b9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/UpdateFirmwareOperation.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/UpdateFirmwareOperation.java @@ -23,6 +23,7 @@ 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.miband2.AbstractMiBand2Operation; +import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.FirmwareType; import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.Mi2FirmwareInfo; import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.MiBand2Support; import nodomain.freeyourgadget.gadgetbridge.util.GB; @@ -113,7 +114,12 @@ public class UpdateFirmwareOperation extends AbstractMiBand2Operation { break; } case MiBand2Service.COMMAND_FIRMWARE_CHECKSUM: { - sendApplyReboot(getFirmwareInfo()); + if (getFirmwareInfo().getFirmwareType() == FirmwareType.FIRMWARE) { + getSupport().onReboot(); + } else { + GB.updateInstallNotification(getContext().getString(R.string.updatefirmwareoperation_update_complete), false, 100, getContext()); + done(); + } break; } case MiBand2Service.COMMAND_FIRMWARE_REBOOT: { @@ -152,12 +158,20 @@ public class UpdateFirmwareOperation extends AbstractMiBand2Operation { builder.add(new SetDeviceBusyAction(getDevice(), getContext().getString(R.string.updating_firmware), getContext())); int fwSize = getFirmwareInfo().getSize(); byte[] sizeBytes = BLETypeConversions.fromUint24(fwSize); - byte[] bytes = new byte[]{ - MiBand2Service.COMMAND_FIRMWARE_INIT, - sizeBytes[0], - sizeBytes[1], - sizeBytes[2], - }; + int arraySize = 4; + boolean isFirmwareCode = getFirmwareInfo().getFirmwareType() == FirmwareType.FIRMWARE; + if (!isFirmwareCode) { + arraySize++; + } + byte[] bytes = new byte[arraySize]; + int i = 0; + bytes[i++] = MiBand2Service.COMMAND_FIRMWARE_INIT; + bytes[i++] = sizeBytes[0]; + bytes[i++] = sizeBytes[1]; + bytes[i++] = sizeBytes[2]; + if (!isFirmwareCode) { + bytes[i++] = getFirmwareInfo().getFirmwareType().getValue(); + } builder.write(fwCControlChar, bytes); builder.queue(getQueue()); @@ -237,12 +251,6 @@ public class UpdateFirmwareOperation extends AbstractMiBand2Operation { builder.queue(getQueue()); } - private void sendApplyReboot(Mi2FirmwareInfo firmwareInfo) throws IOException { - TransactionBuilder builder = performInitialized("send firmware reboot"); - builder.write(fwCControlChar, new byte[] { MiBand2Service.COMMAND_FIRMWARE_REBOOT}); - builder.queue(getQueue()); - } - private Mi2FirmwareInfo getFirmwareInfo() { return firmwareInfo; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/ArrayUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/ArrayUtils.java index d0bee577..a380b8a0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/ArrayUtils.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/ArrayUtils.java @@ -52,4 +52,14 @@ public class ArrayUtils { } return result; } + + /** + * Returns true if the given byte array starts with the given values + * @param array the array to check + * @param values the values which the other array is checked to start with + * @return + */ + public static boolean startsWith(byte[] array, byte[] values) { + return equals(array, values, 0); + } } diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/ArrayUtilsTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/ArrayUtilsTest.java index d6bc5bca..71064f5a 100644 --- a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/ArrayUtilsTest.java +++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/ArrayUtilsTest.java @@ -111,6 +111,46 @@ public class ArrayUtilsTest extends TestBase { assertFalse(ArrayUtils.equals(DATA_5, new byte[] {3, 4, 6}, 2)); } + @Test + public void testStartsWith1() throws Exception { + assertTrue(ArrayUtils.startsWith(DATA_5, new byte[] {1})); + } + + @Test + public void testStartsWith2() throws Exception { + assertTrue(ArrayUtils.startsWith(DATA_5, new byte[] {1, 2})); + } + + @Test + public void testStartsWithAll() throws Exception { + assertTrue(ArrayUtils.startsWith(DATA_5, DATA_5.clone())); + } + + @Test + public void testStartsWithEmpty() throws Exception { + assertTrue(ArrayUtils.startsWith(DATA_5, EMPTY)); + } + + @Test + public void testStartsWithFail1() throws Exception { + try { + ArrayUtils.startsWith(DATA_5, null); + fail("should have thrown an exception"); + } catch (IllegalArgumentException ex) { + // expected + } + } + + @Test + public void testStartsWithFail3() throws Exception { + assertFalse(ArrayUtils.startsWith(DATA_5, new byte[] {2, 3})); + } + + @Test + public void testStartsWithFail4() throws Exception { + assertFalse(ArrayUtils.startsWith(DATA_5, new byte[] {1, 2, 3, 4, 5, 6})); + } + private byte[] b(int b) { return new byte[] {(byte) b}; }