/* 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);
}
}
}
}