/* Copyright (C) 2023-2024 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.operations; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetProgressAction; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsAgpsService; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsFileTransferService; import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus; import nodomain.freeyourgadget.gadgetbridge.util.GB; /** * Updates the AGPS EPO on a Zepp OS device. Update goes as follows: * 1. Request an upload start from {@link ZeppOsAgpsService} * 2. After successful ack from 1, upload the file to agps://upgrade using {@link ZeppOsFileTransferService} * 3. After successful ack from 2, trigger the actual update with {@link ZeppOsAgpsService} * 4. After successful ack from 3, update is finished. Trigger an AGPS config request from {@link ZeppOsConfigService} * to reload the AGPS update and expiration timestamps. */ public class ZeppOsAgpsUpdateOperation extends AbstractBTLEOperation implements ZeppOsFileTransferService.Callback, ZeppOsAgpsService.Callback { private static final Logger LOG = LoggerFactory.getLogger(ZeppOsAgpsUpdateOperation.class); private static final String AGPS_UPDATE_URL = "agps://upgrade"; private static final String AGPS_UPDATE_FILE = "uih.bin"; private final ZeppOsAgpsFile file; private final byte[] fileBytes; private final ZeppOsAgpsService agpsService; private final ZeppOsFileTransferService fileTransferService; private final ZeppOsConfigService configService; public ZeppOsAgpsUpdateOperation(final ZeppOsSupport support, final ZeppOsAgpsFile file, final ZeppOsAgpsService agpsService, final ZeppOsFileTransferService fileTransferService, final ZeppOsConfigService configService) { super(support); this.file = file; this.fileBytes = file.getUihhBytes(); this.agpsService = agpsService; this.fileTransferService = fileTransferService; this.configService = configService; } @Override protected void doPerform() throws IOException { agpsService.setCallback(this); agpsService.startUpload(file.getUihhBytes().length); } @Override protected void operationFinished() { operationStatus = OperationStatus.FINISHED; if (getDevice() != null && getDevice().isConnected()) { unsetBusy(); getDevice().sendDeviceUpdateIntent(getContext()); } } @Override public void onFileUploadFinish(final boolean success) { LOG.info("Finished file upload operation, success={}", success); agpsService.startUpdate(); } @Override public void onFileUploadProgress(final int progress) { LOG.trace("File upload operation progress: {}", progress); // This makes the progress go from 0% to 50%, during file upload the other 50% are incremented // by the update process on the watch final int progressPercent = (int) ((((float) (progress)) / (fileBytes.length * 2)) * 100); updateProgress(progressPercent); } @Override public void onFileDownloadFinish(final String url, final String filename, final byte[] data) { LOG.warn("Received unexpected file: url={} filename={} length={}", url, filename, data.length); } @Override public void onAgpsUploadStartResponse(final boolean success) { if (!success) { onFinish(false); return; } fileTransferService.sendFile(AGPS_UPDATE_URL, AGPS_UPDATE_FILE, fileBytes, false, this); } @Override public void onAgpsProgressResponse(final int size, final int progress) { // First 50% are from file upload, so this one starts at 50% final int progressPercent = (int) ((((float) (size + progress)) / (size * 2)) * 100); updateProgress(progressPercent); } @Override public void onAgpsUpdateFinishResponse(final boolean success) { if (success) { try { final TransactionBuilder builder = performInitialized("request agps config"); configService.requestConfig(builder, ZeppOsConfigService.ConfigGroup.AGPS); builder.queue(getQueue()); } catch (final Exception e) { LOG.error("Failed to request agps config", e); } } onFinish(success); } private void updateProgress(final int progressPercent) { try { final TransactionBuilder builder = performInitialized("send agps update progress"); builder.add(new SetProgressAction(getContext().getString(R.string.updatefirmwareoperation_update_in_progress), true, progressPercent, getContext())); builder.queue(getQueue()); } catch (final Exception e) { LOG.error("Failed to update progress notification", e); } } private void onFinish(final boolean success) { LOG.info("Finished agps update operation, success={}", success); agpsService.setCallback(null); final String notificationMessage = success ? getContext().getString(R.string.updatefirmwareoperation_update_complete) : getContext().getString(R.string.updatefirmwareoperation_write_failed); GB.updateInstallNotification(notificationMessage, false, 100, getContext()); operationFinished(); } }