/* Copyright (C) 2022-2024 Daniel Dakhno, José Rebelo 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.service.devices.huami.zeppos; import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType.AGPS_UIHH; import android.content.Context; import android.graphics.Bitmap; import android.net.Uri; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.Writer; import java.util.Set; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsFwHelper; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp; import nodomain.freeyourgadget.gadgetbridge.model.GenericItem; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; public class ZeppOsFwInstallHandler implements InstallHandler { private static final Logger LOG = LoggerFactory.getLogger(ZeppOsFwInstallHandler.class); private final Uri mUri; private final Context mContext; private final ZeppOsFwHelper mHelper; public ZeppOsFwInstallHandler(final Uri uri, final Context context, final String deviceName, final Set deviceSources) { mUri = uri; mContext = context; mHelper = new ZeppOsFwHelper(uri, context, deviceName, deviceSources); } @Override public boolean isValid() { return mHelper.isValid(); } @Override public void validateInstallation(InstallActivity installActivity, GBDevice device) { if (device.isBusy()) { installActivity.setInfoText(device.getBusyTask()); installActivity.setInstallEnabled(false); return; } if (!device.isInitialized()) { installActivity.setInfoText(mContext.getString(R.string.fwapp_install_device_not_ready)); installActivity.setInstallEnabled(false); return; } if (!mHelper.isValid()) { installActivity.setInfoText(mContext.getString(R.string.fwapp_install_device_not_supported)); installActivity.setInstallEnabled(false); return; } final DeviceCoordinator coordinator = device.getDeviceCoordinator(); final GenericItem fwItem = new GenericItem(mContext.getString( R.string.installhandler_firmware_name, device.getAliasOrName(), mHelper.getFirmwareType(), // TODO human name mHelper.getFirmwareVersion() )); fwItem.setIcon(coordinator.getDefaultIconResource()); final StringBuilder sb = new StringBuilder(); if (!mHelper.getFirmwareType().isWatchface() && !mHelper.getFirmwareType().isApp() && mHelper.getFirmwareType() != AGPS_UIHH) { sb.append(mContext.getString( R.string.fw_upgrade_notice, mContext.getString(R.string.fw_upgrade_notice_zepp_os, mHelper.getFirmwareVersion(), device.getAliasOrName()) )); // TODO whitelisted firmware //if (mHelper.isFirmwareWhitelisted()) { // sb.append(" ").append(mContext.getString(R.string.miband_firmware_known)); // fwItem.setDetails(mContext.getString(R.string.miband_fwinstaller_compatible_version)); // // TODO: set a CHECK (OKAY) button //} else { sb.append(" ").append(mContext.getString(R.string.miband_firmware_unknown_warning)).append(" \n\n") .append(mContext.getString(R.string.miband_firmware_suggest_whitelist, String.valueOf(mHelper.getFirmwareVersion()))); fwItem.setDetails(mContext.getString(R.string.miband_fwinstaller_untested_version)); // TODO: set a UNKNOWN (question mark) button //} } installActivity.setPreview(mHelper.getPreview()); installActivity.setInfoText(sb.toString()); installActivity.setInstallItem(fwItem); installActivity.setInstallEnabled(true); } @Override public void onStartInstall(final GBDevice device) { final boolean shouldCache = mHelper.getFirmwareType().isApp() || mHelper.getFirmwareType().isWatchface(); if (shouldCache) { saveToCache(device); } } private void saveToCache(final GBDevice device) { final DeviceCoordinator coordinator = device.getDeviceCoordinator(); final File appCacheDir; try { appCacheDir = coordinator.getAppCacheDir(); } catch (final IOException e) { LOG.error("Failed to get app cache dir", e); return; } final GBDeviceApp app = mHelper.getAppInfo(); if (app == null) { LOG.warn("Unable to cache app, appInfo is null"); return; } // write app zip final File appOutputFile = new File(appCacheDir, app.getUUID().toString() + coordinator.getAppFileExtension()); try { appCacheDir.mkdirs(); FileUtils.copyURItoFile(mContext, mUri, appOutputFile); } catch (final IOException e) { LOG.error("Failed to save app to cache", e); return; } // write app metadata final File metadataOutputFile = new File(appCacheDir, app.getUUID().toString() + ".json"); try (Writer writer = new BufferedWriter(new FileWriter(metadataOutputFile))) { final JSONObject appJSON = app.getJSON(); writer.write(appJSON.toString()); } catch (final IOException e) { LOG.error("Failed to write app metadata", e); return; } if (app.getPreviewImage() != null) { final File previewOutputFile = new File(appCacheDir, app.getUUID().toString() + "_preview.png"); try (FileOutputStream fos = new FileOutputStream(previewOutputFile)) { app.getPreviewImage().compress(Bitmap.CompressFormat.PNG, 9, fos); } catch (final IOException e) { LOG.error("Failed to write app preview", e); } } } }