1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-12 04:59:27 +01:00

Zepp OS: Fix AGPS uploads

Regression introduced by 34fd18885a - UIHH upload is needed for AGPS
updates on older devices.
This commit is contained in:
José Rebelo 2024-02-02 20:50:58 +00:00
parent 2a377ba5ab
commit 944e0d92a7
2 changed files with 103 additions and 14 deletions

View File

@ -28,12 +28,15 @@ import org.json.JSONObject;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.BufferedInputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.zip.CRC32; import java.util.zip.CRC32;
@ -43,10 +46,13 @@ import java.util.zip.ZipFile;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp; import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.UIHHContainer;
import nodomain.freeyourgadget.gadgetbridge.util.BitmapUtil; import nodomain.freeyourgadget.gadgetbridge.util.BitmapUtil;
import nodomain.freeyourgadget.gadgetbridge.util.CheckSums; import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GBZipFile; import nodomain.freeyourgadget.gadgetbridge.util.GBZipFile;
import nodomain.freeyourgadget.gadgetbridge.util.UriHelper; import nodomain.freeyourgadget.gadgetbridge.util.UriHelper;
import nodomain.freeyourgadget.gadgetbridge.util.ZipFileException;
public class ZeppOsFwHelper { public class ZeppOsFwHelper {
private static final Logger LOG = LoggerFactory.getLogger(ZeppOsFwHelper.class); private static final Logger LOG = LoggerFactory.getLogger(ZeppOsFwHelper.class);
@ -106,7 +112,7 @@ public class ZeppOsFwHelper {
zpkCacheDir.mkdir(); zpkCacheDir.mkdir();
try { try {
file = File.createTempFile("fwhelper","bin", context.getCacheDir()); file = File.createTempFile("fwhelper", "bin", context.getCacheDir());
file.deleteOnExit(); file.deleteOnExit();
} catch (final IOException e) { } catch (final IOException e) {
LOG.error("Failed to create temp file for zpk", e); LOG.error("Failed to create temp file for zpk", e);
@ -136,15 +142,77 @@ public class ZeppOsFwHelper {
return; return;
} }
try (ZipFile zipFile = new ZipFile(file, java.util.zip.ZipFile.OPEN_READ)) { final byte[] header = getHeader(file, 4);
processZipFile(zipFile); if (header == null) {
} catch (final ZipException e) { return;
LOG.warn("{} is not a valid zip file", uri, e);
} catch (final IOException e) {
LOG.warn("Error while processing {}", uri, e);
} }
// TODO process as UIHH if (Arrays.equals(header, GBZipFile.ZIP_HEADER)) {
try (ZipFile zipFile = new ZipFile(file, java.util.zip.ZipFile.OPEN_READ)) {
processZipFile(zipFile);
} catch (final ZipException e) {
LOG.warn("{} is not a valid zip file", uri, e);
} catch (final IOException e) {
LOG.warn("Error while processing {}", uri, e);
}
} else if (Arrays.equals(header, UIHHContainer.UIHH_HEADER)) {
// FIXME: This should be refactored to avoid pulling the entire file to memory
// However, it's currently only used for agps updates, which are usually just ~140KB
try (InputStream in = new BufferedInputStream(uriHelper.openInputStream())) {
final byte[] fullFile = FileUtils.readAll(in, 32 * 1024 * 1024); // 32MB
processAsUihh(fullFile);
} catch (final IOException e) {
LOG.error("Failed to read full uihh from file", e);
}
}
}
private void processAsUihh(byte[] bytes) {
final UIHHContainer uihh = UIHHContainer.fromRawBytes(bytes);
if (uihh == null) {
LOG.warn("Invalid UIHH file");
return;
}
final Set<UIHHContainer.FileType> agpsEpoTypes = new HashSet<>();
UIHHContainer.FileEntry uihhFirmwareZipFile = null;
boolean hasChangelog = false;
for (final UIHHContainer.FileEntry file : uihh.getFiles()) {
switch (file.getType()) {
case FIRMWARE_ZIP:
uihhFirmwareZipFile = file;
continue;
case FIRMWARE_CHANGELOG:
hasChangelog = true;
continue;
case AGPS_EPO_GR_3:
case AGPS_EPO_GAL_7:
case AGPS_EPO_BDS_3:
agpsEpoTypes.add(file.getType());
continue;
default:
LOG.warn("Unexpected file for {}", file.getType());
}
}
if (uihhFirmwareZipFile != null && hasChangelog) {
// UIHH firmware update
final GBZipFile zipFile = new GBZipFile(uihhFirmwareZipFile.getContent());
final byte[] firmwareBin;
try {
firmwareBin = zipFile.getFileFromZip("META/firmware.bin");
} catch (final ZipFileException e) {
LOG.error("Failed to read zip from UIHH", e);
return;
}
if (isCompatibleFirmwareBin(firmwareBin)) {
firmwareType = HuamiFirmwareType.FIRMWARE_UIHH_2021_ZIP_WITH_CHANGELOG;
}
} else if (agpsEpoTypes.size() == 3) {
// AGPS EPO update
firmwareType = HuamiFirmwareType.AGPS_UIHH;
}
} }
private void processZipFile(final ZipFile zipFile) { private void processZipFile(final ZipFile zipFile) {
@ -252,7 +320,7 @@ public class ZeppOsFwHelper {
final File zpkFile; final File zpkFile;
try { try {
zpkFile = File.createTempFile("zpk","zip", context.getCacheDir()); zpkFile = File.createTempFile("zpk", "zip", context.getCacheDir());
zpkFile.deleteOnExit(); zpkFile.deleteOnExit();
} catch (final IOException e) { } catch (final IOException e) {
LOG.error("Failed to create temp file for zpk", e); LOG.error("Failed to create temp file for zpk", e);
@ -432,6 +500,23 @@ public class ZeppOsFwHelper {
} }
} }
@Nullable
public static byte[] getHeader(final File file, final int bytes) {
final byte[] header = new byte[bytes];
try (InputStream is = new FileInputStream(file)) {
if (is.read(header) != header.length) {
LOG.warn("Read unexpected number of header bytes");
return null;
}
} catch (final IOException e) {
LOG.error("Error while reading header bytes", e);
return null;
}
return header;
}
public static boolean searchString(final byte[] fwBytes, final String str) { public static boolean searchString(final byte[] fwBytes, final String str) {
final byte[] strBytes = (str + "\0").getBytes(StandardCharsets.UTF_8); final byte[] strBytes = (str + "\0").getBytes(StandardCharsets.UTF_8);

View File

@ -642,11 +642,13 @@ public final class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTrans
return; return;
} }
new ZeppOsFirmwareUpdateOperation(Uri.parse(uihhFile.toURI().toString()), this).perform(); new ZeppOsFirmwareUpdateOperation(
Uri.parse(uihhFile.toURI().toString()),
this
).perform();
} }
} catch (final Exception e) { } catch (final Exception e) {
GB.toast(getContext(), "AGPS file cannot be installed: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e); GB.toast(getContext(), "AGPS install error: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
} }
return; return;
@ -661,14 +663,16 @@ public final class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTrans
fileTransferService fileTransferService
).perform(); ).perform();
} catch (final Exception e) { } catch (final Exception e) {
GB.toast(getContext(), "Gpx route file cannot be installed: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e); GB.toast(getContext(), "Gpx install error: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
} }
return;
} }
try { try {
new ZeppOsFirmwareUpdateOperation(uri, this).perform(); new ZeppOsFirmwareUpdateOperation(uri, this).perform();
} catch (final IOException ex) { } catch (final IOException ex) {
GB.toast(getContext(), "Firmware cannot be installed: " + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex); GB.toast(getContext(), "Firmware install error: " + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex);
} }
} }