diff --git a/app/build.gradle b/app/build.gradle index 43e1e1097..9633696bf 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -78,6 +78,7 @@ dependencies { implementation "androidx.palette:palette:1.0.0" implementation "com.google.android.material:material:1.3.0" + implementation "com.google.code.gson:gson:2.8.6" implementation "no.nordicsemi.android:dfu:1.11.1" implementation("com.github.tony19:logback-android-classic:1.1.1-6") { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/InfiniTimeDFUPackage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/InfiniTimeDFUPackage.java new file mode 100644 index 000000000..33d0936d4 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/InfiniTimeDFUPackage.java @@ -0,0 +1,43 @@ +/* Copyright (C) 2021 Taavi Eomäe + + 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.devices.pinetime; + +import java.math.BigInteger; +import java.util.List; + +public class InfiniTimeDFUPackage { + InfiniTimeDFUPackageManifest manifest; +} + +class InfiniTimeDFUPackageManifest { + InfiniTimeDFUPackageApplication application; + Float dfu_version; +} + +class InfiniTimeDFUPackageApplication { + String bin_file; + String dat_file; + InfiniTimeDFUPackagePacketData init_packet_data; +} + +class InfiniTimeDFUPackagePacketData { + BigInteger application_version; + BigInteger device_revision; + BigInteger device_type; + BigInteger firmware_crc16; + List softdevice_req; +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/PineTimeInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/PineTimeInstallHandler.java index a4e0a9f7a..7ebca91dd 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/PineTimeInstallHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/PineTimeInstallHandler.java @@ -17,17 +17,18 @@ package nodomain.freeyourgadget.gadgetbridge.devices.pinetime; import android.content.Context; -import android.content.Intent; import android.net.Uri; +import android.os.Build; -import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import com.google.gson.Gson; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.BufferedInputStream; -import java.io.IOException; import java.io.InputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity; @@ -35,9 +36,10 @@ import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; import nodomain.freeyourgadget.gadgetbridge.model.GenericItem; -import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.UriHelper; +import static java.nio.charset.StandardCharsets.UTF_8; + public class PineTimeInstallHandler implements InstallHandler { private static final Logger LOG = LoggerFactory.getLogger(PineTimeInstallHandler.class); @@ -47,49 +49,86 @@ public class PineTimeInstallHandler implements InstallHandler { public PineTimeInstallHandler(Uri uri, Context context) { this.context = context; + UriHelper uriHelper; + InputStream inputStream; + ZipInputStream zipInputStream; + + InfiniTimeDFUPackage metadata = null; try { uriHelper = UriHelper.get(uri, this.context); - } catch (IOException e) { - valid = false; - return; - } - - try (InputStream in = new BufferedInputStream(uriHelper.openInputStream())) { - byte[] bytes = new byte[32]; - int read = in.read(bytes); - if (read < 32) { - valid = false; - return; + inputStream = new BufferedInputStream(uriHelper.openInputStream()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + zipInputStream = new ZipInputStream(inputStream, UTF_8); + } else { + zipInputStream = new ZipInputStream(inputStream); } + + ZipEntry entry; + while ((entry = zipInputStream.getNextEntry()) != null) { + if (entry.isDirectory()) { + continue; + } + if (entry.getName().equals("manifest.json")) { + LOG.debug("Found manifest.json in DFU zip"); + StringBuilder json = new StringBuilder(); + + final byte[] buffer = new byte[1024]; + + while (zipInputStream.read(buffer, 0, buffer.length) != -1) { + json.append(new String(buffer)); + } + + Gson gson = new Gson(); + metadata = gson.fromJson(json.toString().trim(), InfiniTimeDFUPackage.class); + continue; + } + } + + zipInputStream.close(); + inputStream.close(); } catch (Exception e) { valid = false; return; } - valid = true; + + if (metadata != null) { + valid = true; + version = metadata.manifest.application.bin_file; + } } @Override public void validateInstallation(InstallActivity installActivity, GBDevice device) { + installActivity.setInstallEnabled(true); + if (device.isBusy()) { + LOG.error("Firmware cannot be installed (device busy)"); + installActivity.setInfoText("Firmware cannot be installed (device busy)"); installActivity.setInfoText(device.getBusyTask()); installActivity.setInstallEnabled(false); return; } if (device.getType() != DeviceType.PINETIME_JF || !device.isConnected()) { - installActivity.setInfoText("Firmware cannot be installed"); + LOG.error("Firmware cannot be installed (not connected or wrong device)"); + installActivity.setInfoText("Firmware cannot be installed (not connected or wrong device)"); installActivity.setInstallEnabled(false); return; } + if (!valid) { + LOG.error("Firmware cannot be installed (not valid)"); + installActivity.setInfoText("Firmware cannot be installed (not valid)"); + installActivity.setInstallEnabled(false); + } + GenericItem installItem = new GenericItem(); installItem.setIcon(R.drawable.ic_firmware); installItem.setName("PineTime firmware"); installItem.setDetails(version); installActivity.setInfoText(context.getString(R.string.firmware_install_warning, "(unknown)")); - installActivity.setInstallEnabled(true); installActivity.setInstallItem(installItem); LOG.debug("Initialized PineTimeInstallHandler"); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pinetime/PineTimeJFSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pinetime/PineTimeJFSupport.java index bf2dd7fa8..622e21c4b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pinetime/PineTimeJFSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pinetime/PineTimeJFSupport.java @@ -326,8 +326,8 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport implements DfuL try { handler = new PineTimeInstallHandler(uri, getContext()); - // TODO: Check validity more closely - if (true) { + if (handler.isValid()) { + gbDevice.setBusyTask("firmware upgrade"); DfuServiceInitiator starter = new DfuServiceInitiator(getDevice().getAddress()) .setDeviceName(getDevice().getName()) .setKeepBond(true) @@ -346,13 +346,13 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport implements DfuL LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent(GB.ACTION_SET_PROGRESS_TEXT) .putExtra(GB.DISPLAY_MESSAGE_MESSAGE, getContext().getString(R.string.devicestatus_upload_starting)) ); - gbDevice.setBusyTask("firmware upgrade"); } else { - // TODO: Handle invalid firmware files + LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent(GB.ACTION_SET_PROGRESS_TEXT) + .putExtra(GB.DISPLAY_MESSAGE_MESSAGE, getContext().getString(R.string.fwinstaller_firmware_not_compatible_to_device))); } } catch (Exception ex) { GB.toast(getContext(), getContext().getString(R.string.updatefirmwareoperation_write_failed) + ":" + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex); - if (gbDevice.isBusy()) { + if (gbDevice.isBusy() && gbDevice.getBusyTask().equals("firmware upgrade")) { gbDevice.unsetBusyTask(); } }