1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-25 11:26:47 +01:00

Xiaomi: Refactor to install firmware (untested)

This commit is contained in:
José Rebelo 2023-10-23 18:47:13 +01:00
parent c47e830056
commit 29c183b88a
6 changed files with 185 additions and 21 deletions

View File

@ -14,7 +14,7 @@
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.miband8;
package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi;
import android.content.Context;
import android.net.Uri;
@ -36,9 +36,12 @@ public class XiaomiFWHelper {
private final Uri uri;
private byte[] fw;
private boolean valid;
private boolean typeFirmware;
private boolean typeWatchface;
private String id;
private String name;
private String version;
public XiaomiFWHelper(final Uri uri, final Context context) {
this.uri = uri;
@ -65,15 +68,23 @@ public class XiaomiFWHelper {
return;
}
valid = parseFirmware();
parseBytes();
}
public boolean isValid() {
return valid;
}
public boolean isWatchface() {
return typeWatchface;
}
public boolean isFirmware() {
return typeFirmware;
}
public String getDetails() {
return name != null ? name : "UNKNOWN WATCHFACE";
return name != null ? name : (version != null ? version : "UNKNOWN");
}
public byte[] getBytes() {
@ -88,11 +99,29 @@ public class XiaomiFWHelper {
return name;
}
public String getVersion() {
return version;
}
public void unsetFwBytes() {
this.fw = null;
}
private boolean parseFirmware() {
private void parseBytes() {
if (parseAsWatchface()) {
assert id != null;
valid = true;
typeWatchface = true;
} else if (parseAsFirmware()) {
assert version != null;
valid = true;
typeFirmware = true;
} else {
valid = false;
}
}
private boolean parseAsWatchface() {
if (fw[0] != (byte) 0x5A || fw[1] != (byte) 0xA5) {
LOG.warn("File header not a watchface");
return false;
@ -120,4 +149,9 @@ public class XiaomiFWHelper {
return true;
}
private boolean parseAsFirmware() {
// TODO parse and set version
return false;
}
}

View File

@ -22,7 +22,6 @@ import android.net.Uri;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.miband8.XiaomiFWHelper;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
@ -63,8 +62,17 @@ public class XiaomiInstallHandler implements InstallHandler {
}
final GenericItem installItem = new GenericItem();
installItem.setIcon(R.drawable.ic_watchface);
installItem.setName(mContext.getString(R.string.kind_watchface));
if (helper.isWatchface()) {
installItem.setIcon(R.drawable.ic_watchface);
installItem.setName(mContext.getString(R.string.kind_watchface));
} else if (helper.isFirmware()) {
installItem.setIcon(R.drawable.ic_firmware);
installItem.setName(mContext.getString(R.string.kind_firmware));
} else {
installItem.setIcon(R.drawable.ic_device_unknown);
installItem.setName(mContext.getString(R.string.kind_invalid));
}
installItem.setDetails(helper.getDetails());
installActivity.setInfoText(mContext.getString(R.string.firmware_install_warning, "(unknown)"));

View File

@ -27,7 +27,6 @@ import android.net.Uri;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Map;
@ -35,6 +34,7 @@ import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiFWHelper;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
@ -319,8 +319,20 @@ public abstract class XiaomiSupport extends AbstractBTLEDeviceSupport {
@Override
public void onInstallApp(final Uri uri) {
// TODO distinguish between fw and watchface
watchfaceService.installWatchface(uri);
final XiaomiFWHelper fwHelper = new XiaomiFWHelper(uri, getContext());
if (!fwHelper.isValid()) {
LOG.warn("Uri {} is not valid", uri);
return;
}
if (fwHelper.isFirmware()) {
systemService.installFirmware(fwHelper);
} else if (fwHelper.isWatchface()) {
watchfaceService.installWatchface(fwHelper);
} else {
LOG.warn("Unknown fwhelper for {}", uri);
}
}
@Override

View File

@ -30,6 +30,7 @@ import java.util.Map;
import java.util.TimeZone;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabilityImpl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
@ -38,15 +39,21 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdateDevi
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiFWHelper;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetProgressAction;
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiPreferences;
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
public class XiaomiSystemService extends AbstractXiaomiService {
public class XiaomiSystemService extends AbstractXiaomiService implements XiaomiDataUploadService.Callback {
private static final Logger LOG = LoggerFactory.getLogger(XiaomiSystemService.class);
public static final int COMMAND_TYPE = 2;
@ -54,6 +61,7 @@ public class XiaomiSystemService extends AbstractXiaomiService {
public static final int CMD_BATTERY = 1;
public static final int CMD_DEVICE_INFO = 2;
public static final int CMD_CLOCK = 3;
public static final int CMD_FIRMWARE_INSTALL = 5;
public static final int CMD_LANGUAGE = 6;
public static final int CMD_PASSWORD_GET = 9;
public static final int CMD_FIND_PHONE = 17;
@ -63,6 +71,9 @@ public class XiaomiSystemService extends AbstractXiaomiService {
public static final int CMD_DISPLAY_ITEMS_SET = 30;
public static final int CMD_CHARGER = 79;
// Not null if we're installing a firmware
private XiaomiFWHelper fwHelper = null;
public XiaomiSystemService(final XiaomiSupport support) {
super(support);
}
@ -86,6 +97,18 @@ public class XiaomiSystemService extends AbstractXiaomiService {
case CMD_BATTERY:
handleBattery(cmd.getSystem().getPower().getBattery());
return;
case CMD_FIRMWARE_INSTALL:
final int installStatus = cmd.getSystem().getFirmwareInstallResponse().getStatus();
if (installStatus != 0) {
LOG.warn("Invalid firmware install status {} for {}", installStatus, fwHelper.getId());
return;
}
LOG.debug("Firmware install status 0, uploading");
setDeviceBusy();
getSupport().getDataUploader().setCallback(this);
getSupport().getDataUploader().requestUpload(XiaomiDataUploadService.TYPE_FIRMWARE, fwHelper.getBytes());
return;
case CMD_PASSWORD_GET:
handlePassword(cmd.getSystem().getPassword());
return;
@ -400,6 +423,82 @@ public class XiaomiSystemService extends AbstractXiaomiService {
);
}
public void installFirmware(final XiaomiFWHelper fwHelper) {
assert fwHelper.isValid();
assert fwHelper.isFirmware();
this.fwHelper = fwHelper;
getSupport().sendCommand(
"install firmware " + fwHelper.getVersion(),
XiaomiProto.Command.newBuilder()
.setType(COMMAND_TYPE)
.setSubtype(CMD_FIRMWARE_INSTALL)
.setSystem(XiaomiProto.System.newBuilder().setFirmwareInstallRequest(
XiaomiProto.FirmwareInstallRequest.newBuilder()
.setUnknown1(0)
.setUnknown2(0)
.setVersion(fwHelper.getVersion())
.setMd5(GB.hexdump(CheckSums.md5(fwHelper.getBytes())).toLowerCase(Locale.ROOT))
))
.build()
);
}
private void setDeviceBusy() {
final GBDevice device = getSupport().getDevice();
device.setBusyTask(getSupport().getContext().getString(R.string.updating_firmware));
device.sendDeviceUpdateIntent(getSupport().getContext());
}
private void unsetDeviceBusy() {
final GBDevice device = getSupport().getDevice();
if (device != null && device.isConnected()) {
if (device.isBusy()) {
device.unsetBusyTask();
device.sendDeviceUpdateIntent(getSupport().getContext());
}
device.sendDeviceUpdateIntent(getSupport().getContext());
}
}
@Override
public void onUploadFinish(final boolean success) {
LOG.debug("Firmware upload finished: {}", success);
getSupport().getDataUploader().setCallback(null);
final String notificationMessage = success ?
getSupport().getContext().getString(R.string.updatefirmwareoperation_update_complete) :
getSupport().getContext().getString(R.string.updatefirmwareoperation_write_failed);
GB.updateInstallNotification(notificationMessage, false, 100, getSupport().getContext());
unsetDeviceBusy();
if (success) {
// TODO do we need to reboot?
}
fwHelper = null;
}
@Override
public void onUploadProgress(final int progressPercent) {
try {
final TransactionBuilder builder = getSupport().createTransactionBuilder("send data upload progress");
builder.add(new SetProgressAction(
getSupport().getContext().getString(R.string.updatefirmwareoperation_update_in_progress),
true,
progressPercent,
getSupport().getContext()
));
builder.queue(getSupport().getQueue());
} catch (final Exception e) {
LOG.error("Failed to update progress notification", e);
}
}
private static final Map<String, String> DISPLAY_ITEM_NAMES = new HashMap<String, String>() {{
put("today_act", "Stats");
put("sport", "Workout");

View File

@ -16,8 +16,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services;
import android.net.Uri;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -29,7 +27,7 @@ import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo;
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.miband8.XiaomiFWHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiFWHelper;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
@ -190,13 +188,11 @@ public class XiaomiWatchfaceService extends AbstractXiaomiService implements Xia
);
}
public void installWatchface(final Uri uri) {
fwHelper = new XiaomiFWHelper(uri, getSupport().getContext());
if (!fwHelper.isValid()) {
fwHelper = null;
LOG.warn("watchface is not valid");
return;
}
public void installWatchface(final XiaomiFWHelper fwHelper) {
assert fwHelper.isValid();
assert fwHelper.isWatchface();
this.fwHelper = fwHelper;
getSupport().sendCommand(
"install watchface " + fwHelper.getId(),

View File

@ -88,6 +88,10 @@ message System {
// 2, 34
optional DoNotDisturb dndStatus = 11;
// 2, 5
optional FirmwareInstallRequest firmwareInstallRequest = 16;
optional FirmwareInstallResponse firmwareInstallResponse = 17;
// 2, 9 get | 2, 21 set
optional Password password = 19;
@ -236,6 +240,17 @@ message DoNotDisturb2 {
message DndSync {
}
message FirmwareInstallRequest {
optional uint32 unknown1 = 1; // 0
optional uint32 unknown2 = 2; // 0
optional string version = 3;
optional string md5 = 4;
}
message FirmwareInstallResponse {
optional uint32 status = 1; // 0
}
message Password {
optional uint32 state = 1; // 1 disabled, 2 enabled
optional string password = 2;