diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWInstallHandler.java index 01e5681fb1..395891538a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWInstallHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWInstallHandler.java @@ -89,7 +89,12 @@ public class PBWInstallHandler implements InstallHandler { } if (!mPBWReader.isValid()) { - installActivity.setInfoText("pbw/pbz is broken or incompatible with your Hardware or Firmware."); + String reason = mPBWReader.getInvalidReason(); + if (reason != null) { + installActivity.setInfoText("Invalid: " + reason); + } else { + installActivity.setInfoText("pbw/pbz is broken or incompatible with your Hardware or Firmware."); + } installActivity.setInstallEnabled(false); return; } @@ -101,7 +106,10 @@ public class PBWInstallHandler implements InstallHandler { installItem.setIcon(R.drawable.ic_firmware); String hwRevision = mPBWReader.getHWRevision(); - if (hwRevision != null && hwRevision.equals(device.getModel())) { + String deviceModel = device.getModel(); + + // Use platform-aware compatibility check instead of exact string match + if (PebbleHardware.isFirmwareCompatible(hwRevision, deviceModel)) { installItem.setName(mContext.getString(R.string.pbw_installhandler_pebble_firmware, "")); installItem.setDetails(mContext.getString(R.string.pbwinstallhandler_correct_hw_revision)); @@ -112,7 +120,13 @@ public class PBWInstallHandler implements InstallHandler { installItem.setName(mContext.getString(R.string.pbw_installhandler_pebble_firmware, hwRevision)); installItem.setDetails(mContext.getString(R.string.pbwinstallhandler_incorrect_hw_revision)); } - installActivity.setInfoText(mContext.getString(R.string.pbw_install_handler_hw_revision_mismatch)); + // Show detailed incompatibility reason + String reason = PebbleHardware.getFirmwareIncompatibilityReason(hwRevision, deviceModel); + if (reason != null) { + installActivity.setInfoText(reason); + } else { + installActivity.setInfoText(mContext.getString(R.string.pbw_install_handler_hw_revision_mismatch)); + } installActivity.setInstallEnabled(false); } } else { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWReader.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWReader.java index 32d641703b..cae4988c98 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWReader.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWReader.java @@ -66,6 +66,7 @@ public class PBWReader { private boolean isFirmware = false; private boolean isLanguage = false; private boolean isValid = false; + private String invalidReason = null; private String hwRevision = null; private short mSdkVersion; private short mAppVersion; @@ -102,8 +103,13 @@ public class PBWReader { platformDir = determinePlatformDir(uriHelper, platform); if (platform.equals("chalk") && platformDir.equals("")) { + invalidReason = "No chalk/ directory found - app doesn't support Pebble Time Round"; return; } + + if (platformDir.equals("")) { + LOG.info("No platform-specific directory found for {}, will try root", platform); + } } LOG.info("using platformdir: '" + platformDir + "'"); @@ -160,6 +166,7 @@ public class PBWReader { } catch (JSONException e) { // no JSON at all that is a problem isValid = false; + invalidReason = "Failed to parse manifest.json: " + e.getMessage(); LOG.warn("exception 1 in constructor", e); break; } @@ -190,6 +197,7 @@ public class PBWReader { } } catch (JSONException e) { isValid = false; + invalidReason = "Failed to parse appinfo.json: " + e.getMessage(); LOG.warn("exception 2 in constructor", e); break; } @@ -227,6 +235,28 @@ public class PBWReader { } else if (!isFirmware) { isValid = false; + StringBuilder missing = new StringBuilder(); + if (appUUID == null) missing.append("uuid, "); + if (appName == null) missing.append("shortName, "); + if (appCreator == null) missing.append("companyName, "); + if (appVersion == null) missing.append("versionLabel, "); + if (missing.length() > 0) { + missing.setLength(missing.length() - 2); // Remove trailing ", " + invalidReason = "Missing required fields in appinfo.json: " + missing; + } else if (pebbleInstallables.isEmpty()) { + invalidReason = "No installable files found in manifest.json for platform"; + } else { + invalidReason = "Unknown app parsing error"; + } + } + } + + // Set default invalid reason if still not valid and no specific reason + if (!isValid && invalidReason == null) { + if (pebbleInstallables == null || pebbleInstallables.isEmpty()) { + invalidReason = "No manifest.json found or no installable files for platform: " + platform; + } else { + invalidReason = "Unknown validation error"; } } } @@ -281,6 +311,14 @@ public class PBWReader { return isValid; } + /** + * Get the reason why this pbw/pbz is invalid. + * @return Human-readable reason string, or null if valid + */ + public String getInvalidReason() { + return invalidReason; + } + public GBDeviceApp getGBDeviceApp() { return app; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleHardware.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleHardware.java index b92205e213..b77ee65ac9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleHardware.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleHardware.java @@ -555,6 +555,70 @@ public class PebbleHardware { return hw.getPlatform().hasHealth(); } + /** + * Check if a firmware's hwRevision is compatible with a device's model. + * Firmware uses platform names (emery), device uses codenames (obelix_pvt). + * + * @param firmwareHwRev The hwrev from firmware manifest (e.g., "emery") + * @param deviceModel The model from GBDevice (e.g., "obelix_pvt") + * @return true if compatible, false otherwise + */ + public static boolean isFirmwareCompatible(@Nullable String firmwareHwRev, @Nullable String deviceModel) { + if (firmwareHwRev == null || deviceModel == null) { + return false; + } + + // Get platforms for both + HardwareRevision fwHw = getByModelString(firmwareHwRev); + HardwareRevision deviceHw = getByModelString(deviceModel); + + if (fwHw == null || deviceHw == null) { + LOG.warn("isFirmwareCompatible: Could not resolve platform - fw={} ({}), device={} ({})", + firmwareHwRev, fwHw, deviceModel, deviceHw); + return false; + } + + boolean compatible = fwHw.getPlatform() == deviceHw.getPlatform(); + LOG.info("isFirmwareCompatible: fw={} ({}), device={} ({}) -> {}", + firmwareHwRev, fwHw.getPlatformName(), deviceModel, deviceHw.getPlatformName(), compatible); + return compatible; + } + + /** + * Get a detailed incompatibility reason for firmware/device mismatch. + * + * @param firmwareHwRev The hwrev from firmware manifest + * @param deviceModel The model from GBDevice + * @return Human-readable reason string, or null if compatible + */ + @Nullable + public static String getFirmwareIncompatibilityReason(@Nullable String firmwareHwRev, @Nullable String deviceModel) { + if (firmwareHwRev == null) { + return "Firmware has no hardware revision specified"; + } + if (deviceModel == null) { + return "Device model is unknown"; + } + + HardwareRevision fwHw = getByModelString(firmwareHwRev); + HardwareRevision deviceHw = getByModelString(deviceModel); + + if (fwHw == null) { + return "Unknown firmware platform: " + firmwareHwRev; + } + if (deviceHw == null) { + return "Unknown device model: " + deviceModel; + } + + if (fwHw.getPlatform() != deviceHw.getPlatform()) { + return String.format("Platform mismatch: firmware is for %s (%s), device is %s (%s)", + fwHw.getPlatform().getDisplayName(), firmwareHwRev, + deviceHw.getPlatform().getDisplayName(), deviceModel); + } + + return null; // Compatible + } + /** * Check if manufacturer data indicates any Pebble device. *