diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiCoordinator.java
index 0dc89863f..5fde67d31 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiCoordinator.java
@@ -16,6 +16,7 @@
along with this program. If not, see . */
package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi;
+import android.app.Activity;
import android.bluetooth.le.ScanFilter;
import androidx.annotation.NonNull;
@@ -23,6 +24,8 @@ import androidx.annotation.Nullable;
import org.apache.commons.lang3.ArrayUtils;
+import java.io.File;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -30,6 +33,7 @@ import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AppManagerActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.capabilities.HeartRateCapability;
import nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabilityImpl;
@@ -48,11 +52,13 @@ import nodomain.freeyourgadget.gadgetbridge.model.SleepRespiratoryRateSample;
import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiLanguageType;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
public abstract class XiaomiCoordinator extends AbstractBLEDeviceCoordinator {
@NonNull
@Override
public Collection extends ScanFilter> createBLEScanFilters() {
+ // TODO
return super.createBLEScanFilters();
}
@@ -131,10 +137,41 @@ public abstract class XiaomiCoordinator extends AbstractBLEDeviceCoordinator {
return true;
}
- @Override
public boolean supportsAppsManagement(final GBDevice device) {
- // TODO maybe for watchfaces or widgets?
- return super.supportsAppsManagement(device);
+ return true;
+ }
+
+ @Override
+ public Class extends Activity> getAppsManagementActivity() {
+ return AppManagerActivity.class;
+ }
+
+ @Override
+ public File getAppCacheDir() throws IOException {
+ // TODO we don't need this
+ return new File(FileUtils.getExternalFilesDir(), "xiaomi-app-cache");
+ }
+
+ @Override
+ public String getAppCacheSortFilename() {
+ // TODO we don't need this
+ return "xiaomi-app-cache-order.txt";
+ }
+
+ @Override
+ public String getAppFileExtension() {
+ // TODO we don't need this
+ return ".bin";
+ }
+
+ @Override
+ public boolean supportsAppListFetching() {
+ return true;
+ }
+
+ @Override
+ public boolean supportsAppReordering() {
+ return false;
}
@Override
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiInstallHandler.java
new file mode 100644
index 000000000..79ca4cb44
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiInstallHandler.java
@@ -0,0 +1,55 @@
+/* Copyright (C) 2023 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.devices.xiaomi;
+
+import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType.AGPS_UIHH;
+
+import android.content.Context;
+import android.net.Uri;
+
+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.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
+
+public class XiaomiInstallHandler implements InstallHandler {
+ protected final Uri mUri;
+ protected final Context mContext;
+
+ public XiaomiInstallHandler(final Uri uri, final Context context) {
+ this.mUri = uri;
+ this.mContext = context;
+ }
+
+ @Override
+ public boolean isValid() {
+ // TODO
+ return false;
+ }
+
+ @Override
+ public void validateInstallation(final InstallActivity installActivity, final GBDevice device) {
+ // TODO
+ }
+
+ @Override
+ public void onStartInstall(final GBDevice device) {
+ // nothing to do
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband8/MiBand8Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband8/MiBand8Coordinator.java
index 63afd1f52..7f0f0d9ce 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband8/MiBand8Coordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/miband8/MiBand8Coordinator.java
@@ -27,6 +27,7 @@ import java.util.regex.Pattern;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiInstallHandler;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiEncryptedSupport;
@@ -39,8 +40,8 @@ public class MiBand8Coordinator extends XiaomiCoordinator {
@Nullable
@Override
public InstallHandler findInstallHandler(final Uri uri, final Context context) {
- // TODO implement this
- return super.findInstallHandler(uri, context);
+ final XiaomiInstallHandler handler = new XiaomiInstallHandler(uri, context);
+ return handler.isValid() ? handler : null;
}
@Override
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSupport.java
index 8c5e45785..9de912287 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSupport.java
@@ -57,6 +57,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.Xiao
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.XiaomiNotificationService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.XiaomiScheduleService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.XiaomiSystemService;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.XiaomiWatchfaceService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.XiaomiWeatherService;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
@@ -77,6 +78,7 @@ public abstract class XiaomiSupport extends AbstractBTLEDeviceSupport {
protected final XiaomiWeatherService weatherService = new XiaomiWeatherService(this);
protected final XiaomiSystemService systemService = new XiaomiSystemService(this);
protected final XiaomiCalendarService calendarService = new XiaomiCalendarService(this);
+ protected final XiaomiWatchfaceService watchfaceService = new XiaomiWatchfaceService(this);
private String mFirmwareVersion = null;
@@ -89,6 +91,7 @@ public abstract class XiaomiSupport extends AbstractBTLEDeviceSupport {
put(XiaomiWeatherService.COMMAND_TYPE, weatherService);
put(XiaomiSystemService.COMMAND_TYPE, systemService);
put(XiaomiCalendarService.COMMAND_TYPE, calendarService);
+ put(XiaomiWatchfaceService.COMMAND_TYPE, watchfaceService);
}};
public XiaomiSupport() {
@@ -324,44 +327,24 @@ public abstract class XiaomiSupport extends AbstractBTLEDeviceSupport {
@Override
public void onInstallApp(final Uri uri) {
- // TODO
- super.onInstallApp(uri);
+ watchfaceService.installWatchface(uri);
}
@Override
public void onAppInfoReq() {
- // TODO
- super.onAppInfoReq();
+ watchfaceService.requestWatchfaceList();
}
@Override
public void onAppStart(final UUID uuid, boolean start) {
- // TODO
- super.onAppStart(uuid, start);
- }
-
- @Override
- public void onAppDownload(final UUID uuid) {
- // TODO
- super.onAppDownload(uuid);
+ if (start) {
+ watchfaceService.setWatchface(uuid);
+ }
}
@Override
public void onAppDelete(final UUID uuid) {
- // TODO
- super.onAppDelete(uuid);
- }
-
- @Override
- public void onAppConfiguration(final UUID appUuid, String config, Integer id) {
- // TODO
- super.onAppConfiguration(appUuid, config, id);
- }
-
- @Override
- public void onAppReorder(final UUID[] uuids) {
- // TODO
- super.onAppReorder(uuids);
+ watchfaceService.deleteWatchface(uuid);
}
@Override
@@ -369,12 +352,6 @@ public abstract class XiaomiSupport extends AbstractBTLEDeviceSupport {
healthService.onFetchRecordedData(dataTypes);
}
- @Override
- public void onReset(final int flags) {
- // TODO
- super.onReset(flags);
- }
-
@Override
public void onHeartRateTest() {
healthService.onHeartRateTest();
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiWatchfaceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiWatchfaceService.java
new file mode 100644
index 000000000..f1898d658
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiWatchfaceService.java
@@ -0,0 +1,123 @@
+/* Copyright (C) 2023 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.xiaomi.services;
+
+import android.net.Uri;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
+import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class XiaomiWatchfaceService extends AbstractXiaomiService {
+ private static final Logger LOG = LoggerFactory.getLogger(XiaomiWatchfaceService.class);
+
+ public static final int COMMAND_TYPE = 4;
+
+ public static final int CMD_WATCHFACE_LIST = 0;
+ public static final int CMD_WATCHFACE_SET = 1;
+ public static final int CMD_WATCHFACE_INSTALL = 4;
+
+
+ public XiaomiWatchfaceService(final XiaomiSupport support) {
+ super(support);
+ }
+
+ @Override
+ public void initialize(final TransactionBuilder builder) {
+ //getSupport().sendCommand(builder, COMMAND_TYPE, CMD_WATCHFACE_LIST);
+ }
+
+ @Override
+ public void handleCommand(final XiaomiProto.Command cmd) {
+ // TODO
+ switch (cmd.getSubtype()) {
+ case CMD_WATCHFACE_LIST:
+ handleWatchfaceList(cmd.getWatchface().getWatchfaceList());
+ // TODO handle
+ return;
+ case CMD_WATCHFACE_SET:
+ // watchface set ack
+ return;
+ case CMD_WATCHFACE_INSTALL:
+ return;
+ }
+
+ LOG.warn("Unknown watchface command {}", cmd.getSubtype());
+ }
+
+ @Override
+ public boolean onSendConfiguration(final String config, final Prefs prefs) {
+ // TODO set watchface
+ return super.onSendConfiguration(config, prefs);
+ }
+
+ public void requestWatchfaceList() {
+ getSupport().sendCommand("request watchface list", COMMAND_TYPE, CMD_WATCHFACE_LIST);
+ }
+
+ private void handleWatchfaceList(final XiaomiProto.WatchfaceList watchfaceList) {
+ LOG.debug("Got {} watchfaces", watchfaceList.getWatchfaceCount());
+
+ final List gbDeviceApps = new ArrayList<>();
+
+ for (final XiaomiProto.WatchfaceInfo watchface : watchfaceList.getWatchfaceList()) {
+ GBDeviceApp gbDeviceApp = new GBDeviceApp(
+ UUID.randomUUID(),
+ watchface.getName(),
+ "",
+ "",
+ GBDeviceApp.Type.WATCHFACE
+ );
+ gbDeviceApps.add(gbDeviceApp);
+ }
+
+ final GBDeviceEventAppInfo appInfoCmd = new GBDeviceEventAppInfo();
+ appInfoCmd.apps = gbDeviceApps.toArray(new GBDeviceApp[0]);
+ getSupport().evaluateGBDeviceEvent(appInfoCmd);
+ }
+
+ public void setWatchface(final UUID uuid) {
+ // TODO
+ }
+
+ public void setWatchface(final String watchfaceId) {
+ // TODO
+ }
+
+ public void deleteWatchface(final UUID uuid) {
+ // TODO
+ }
+
+ public void deleteWatchface(final String watchfaceId) {
+ // TODO
+ // TODO prevent uninstall of non-uninstallable watchface
+ }
+
+ public void installWatchface(final Uri uri) {
+ // TODO
+ }
+}
diff --git a/app/src/main/proto/xiaomi.proto b/app/src/main/proto/xiaomi.proto
index d8325375a..083384c6c 100644
--- a/app/src/main/proto/xiaomi.proto
+++ b/app/src/main/proto/xiaomi.proto
@@ -11,6 +11,7 @@ message Command {
optional Auth auth = 3;
optional System system = 4;
+ optional Watchface watchface = 6;
optional Health health = 10;
optional Calendar calendar = 14;
optional Music music = 20;
@@ -277,6 +278,49 @@ message Charger {
optional uint32 state = 1; // 1 charging, 2 not charging
}
+//
+// Watchface
+//
+
+message Watchface {
+ optional WatchfaceList watchfaceList = 1;
+
+ // 4, 2 delete | 4, 1 set
+ optional string watchfaceId = 2;
+ optional uint32 ack = 4; // 1
+
+ // 4, 4
+ optional uint32 installStatus = 5; // 0 not installed, 2 already installed
+ optional WatchfaceInstallStart watchfaceInstallStart = 6;
+ optional WatchfaceInstallFinish watchfaceInstallFinish = 7;
+}
+
+message WatchfaceList {
+ repeated WatchfaceInfo watchface = 1;
+}
+
+message WatchfaceInfo {
+ optional string id = 1;
+ optional string name = 2;
+ optional bool active = 3;
+ optional bool canDelete = 4;
+ optional uint32 unknown5 = 5; // 0
+ optional uint32 unknown6 = 6; // 0
+ optional uint32 unknown11 = 11; // 0
+}
+
+message WatchfaceInstallStart {
+ optional string id = 1;
+ optional uint32 size = 2;
+}
+
+message WatchfaceInstallFinish {
+ optional string id = 1;
+ optional uint32 unknown2 = 2; // 2
+ optional uint32 unknown3 = 3; // 0
+ optional uint32 unknown4 = 4; // 0
+}
+
//
// Health
//
@@ -706,7 +750,7 @@ message DataUpload {
}
message DataUploadRequest {
- optional uint32 unknown1 = 1; // 16 for watchface, 50 for notification icons, 32 for firmware?
+ optional uint32 type = 1; // 16 for watchface, 50 for notification icons, 32 for firmware?
optional bytes md5sum = 2;
optional uint32 size = 3;
}