Xiaomi: Fix RPK install/delete and enable App Manager UI

- Enable supportsInstalledAppManagement() to show the Apps tab in App Manager
- Fix RPK install protocol: include versionCode (field 2 of RpkInfo) in the
  install request; the band silently ignores requests with versionCode=0
- Fix CMD_RPK_INSTALLED (subtype=2): handle band's post-install notification
  to refresh the app list at the right time
- Fix deleteRpk() UUID comparison: use .equals() instead of ==
- Refresh app list after delete (band sends no explicit delete response)
- Read versionCode from RPK manifest.json in XiaomiFWHelper
- Fix various wrong proto field reads (watchface fields used for RPK responses)
This commit is contained in:
Edoardo Vacchi
2026-03-23 23:20:42 +01:00
parent 21f719851b
commit 36e23fe08c
3 changed files with 32 additions and 8 deletions
@@ -235,7 +235,7 @@ public abstract class XiaomiCoordinator extends AbstractBLEDeviceCoordinator {
@Override
public boolean supportsInstalledAppManagement(@NonNull final GBDevice device) {
return false;
return true;
}
@Override
@@ -94,6 +94,7 @@ public class XiaomiFWHelper {
private String id;
private String name;
private String version;
private int versionCode;
public XiaomiFWHelper(final Uri uri, final Context context) {
this.uri = uri;
@@ -159,6 +160,10 @@ public class XiaomiFWHelper {
return version;
}
public int getVersionCode() {
return versionCode;
}
public void unsetFwBytes() {
this.fw = null;
}
@@ -339,11 +344,15 @@ public class XiaomiFWHelper {
try {
GBZipFile file = new GBZipFile(fw);
String manifest = new String(file.getFileFromZip("manifest.json"));
LOG.debug("RPK manifest: {}", manifest);
JSONObject json = new JSONObject(manifest);
id = json.getString("package");
name = json.getString("name");
version = json.getString("versionName");
versionCode = json.optInt("versionCode", 1);
LOG.debug("Parsed RPK: id={}, name={}, version={}, versionCode={}", id, name, version, versionCode);
} catch (ZipFileException | JSONException e) {
LOG.warn("Failed to parse as RPK: {}", e.getMessage());
return false;
}
return true;
@@ -399,6 +408,7 @@ public class XiaomiFWHelper {
version = new String(fw, 4, 11, StandardCharsets.UTF_8);
return true;
}
LOG.warn("File header not a firmware");
return false;
}
}
@@ -46,6 +46,7 @@ public class XiaomiRpkService extends AbstractXiaomiService implements XiaomiDat
public static final int CMD_RPK_SET = 1;
public static final int CMD_RPK_DELETE = 3;
public static final int CMD_RPK_INSTALL = 1;
public static final int CMD_RPK_INSTALLED = 2;
// Not null if we're installing a firmware
@@ -64,18 +65,23 @@ public class XiaomiRpkService extends AbstractXiaomiService implements XiaomiDat
case CMD_RPK_LIST:
handleRpkList(cmd.getRpk().getRpkList());
return;
case CMD_RPK_INSTALLED:
LOG.debug("Got rpk install complete notification");
requestRpkList();
return;
case CMD_RPK_DELETE:
LOG.debug("Got rpk delete response, ack={}", cmd.getWatchface().getAck());
// Band may send a delete response in some cases
LOG.debug("Got rpk delete response");
requestRpkList();
return;
case CMD_RPK_INSTALL:
final int installStatus = cmd.getWatchface().getInstallStatus();
final int installStatus = cmd.getRpk().getRpkInstallStart().getCmd();
LOG.debug("Got rpk install response, status={}", installStatus);
if (installStatus != 0) {
LOG.warn("Invalid watchface install status {} for {}", installStatus, fwHelper.getId());
LOG.warn("Rpk install rejected with status {} for {}", installStatus, fwHelper != null ? fwHelper.getId() : "null");
return;
}
LOG.debug("Rpk install status 0, uploading");
LOG.debug("Rpk install accepted, starting upload");
setDeviceBusy();
getSupport().getDataUploadService().setCallback(this);
getSupport().getDataUploadService().requestUpload(XiaomiDataUploadService.TYPE_RPK, fwHelper.getBytes());
@@ -91,9 +97,10 @@ public class XiaomiRpkService extends AbstractXiaomiService implements XiaomiDat
public void deleteRpk(UUID uuid) {
for (GBDeviceApp app : apps) {
if (app.getUUID() == uuid) {
if (app.getUUID().equals(uuid)) {
String packageName = app.getCreator();
ByteString sha = shaMap.get(packageName);
LOG.debug("Deleting rpk: id={}", packageName);
getSupport().sendCommand("delete rpk",
XiaomiProto.Command.newBuilder()
.setType(COMMAND_TYPE)
@@ -106,6 +113,8 @@ public class XiaomiRpkService extends AbstractXiaomiService implements XiaomiDat
)
)
.build());
// Band sends no response to delete — request list immediately after
requestRpkList();
return;
}
}
@@ -118,6 +127,7 @@ public class XiaomiRpkService extends AbstractXiaomiService implements XiaomiDat
this.fwHelper = fwHelper;
LOG.debug("Sending install request for rpk: id={}, versionCode={}, size={}", fwHelper.getId(), fwHelper.getVersionCode(), fwHelper.getBytes().length);
getSupport().sendCommand("install rpk " + fwHelper.getId(),
XiaomiProto.Command.newBuilder()
.setType(COMMAND_TYPE)
@@ -126,6 +136,7 @@ public class XiaomiRpkService extends AbstractXiaomiService implements XiaomiDat
.setRpkInfo(
XiaomiProto.RpkInfo.newBuilder()
.setId(fwHelper.getId())
.setUnknown2(fwHelper.getVersionCode())
.setSize(fwHelper.getBytes().length)
))
.build());
@@ -160,6 +171,9 @@ public class XiaomiRpkService extends AbstractXiaomiService implements XiaomiDat
LOG.debug("Rpk upload finished: {}", success);
getSupport().getDataUploadService().setCallback(null);
unsetDeviceBusy();
// List refresh is triggered by CMD_RPK_INSTALLED notification from the band
fwHelper = null;
});
}
@@ -177,7 +191,7 @@ public class XiaomiRpkService extends AbstractXiaomiService implements XiaomiDat
private void setDeviceBusy() {
final GBDevice device = getSupport().getDevice();
device.setBusyTask(getSupport().getContext().getString(R.string.uploading_rpk));
device.setBusyTask(R.string.uploading_rpk, getSupport().getContext());
device.sendDeviceUpdateIntent(getSupport().getContext());
}