1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-12-27 02:55:50 +01:00

Mi Band 8: List watchface in app management

This commit is contained in:
José Rebelo 2023-10-21 22:07:06 +01:00
parent 9ea7b48e0c
commit 4e680cfcce
6 changed files with 275 additions and 38 deletions

View File

@ -16,6 +16,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */ along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi; package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi;
import android.app.Activity;
import android.bluetooth.le.ScanFilter; import android.bluetooth.le.ScanFilter;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -23,6 +24,8 @@ import androidx.annotation.Nullable;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@ -30,6 +33,7 @@ import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBException; import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AppManagerActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.capabilities.HeartRateCapability; import nodomain.freeyourgadget.gadgetbridge.capabilities.HeartRateCapability;
import nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabilityImpl; 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.Spo2Sample;
import nodomain.freeyourgadget.gadgetbridge.model.StressSample; import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiLanguageType; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiLanguageType;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
public abstract class XiaomiCoordinator extends AbstractBLEDeviceCoordinator { public abstract class XiaomiCoordinator extends AbstractBLEDeviceCoordinator {
@NonNull @NonNull
@Override @Override
public Collection<? extends ScanFilter> createBLEScanFilters() { public Collection<? extends ScanFilter> createBLEScanFilters() {
// TODO
return super.createBLEScanFilters(); return super.createBLEScanFilters();
} }
@ -131,10 +137,41 @@ public abstract class XiaomiCoordinator extends AbstractBLEDeviceCoordinator {
return true; return true;
} }
@Override
public boolean supportsAppsManagement(final GBDevice device) { public boolean supportsAppsManagement(final GBDevice device) {
// TODO maybe for watchfaces or widgets? return true;
return super.supportsAppsManagement(device); }
@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 @Override

View File

@ -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 <http://www.gnu.org/licenses/>. */
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
}
}

View File

@ -27,6 +27,7 @@ import java.util.regex.Pattern;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiCoordinator; 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.DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiEncryptedSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiEncryptedSupport;
@ -39,8 +40,8 @@ public class MiBand8Coordinator extends XiaomiCoordinator {
@Nullable @Nullable
@Override @Override
public InstallHandler findInstallHandler(final Uri uri, final Context context) { public InstallHandler findInstallHandler(final Uri uri, final Context context) {
// TODO implement this final XiaomiInstallHandler handler = new XiaomiInstallHandler(uri, context);
return super.findInstallHandler(uri, context); return handler.isValid() ? handler : null;
} }
@Override @Override

View File

@ -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.XiaomiNotificationService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.XiaomiScheduleService; 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.XiaomiSystemService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.XiaomiWatchfaceService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.XiaomiWeatherService; import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.XiaomiWeatherService;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs; 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 XiaomiWeatherService weatherService = new XiaomiWeatherService(this);
protected final XiaomiSystemService systemService = new XiaomiSystemService(this); protected final XiaomiSystemService systemService = new XiaomiSystemService(this);
protected final XiaomiCalendarService calendarService = new XiaomiCalendarService(this); protected final XiaomiCalendarService calendarService = new XiaomiCalendarService(this);
protected final XiaomiWatchfaceService watchfaceService = new XiaomiWatchfaceService(this);
private String mFirmwareVersion = null; private String mFirmwareVersion = null;
@ -89,6 +91,7 @@ public abstract class XiaomiSupport extends AbstractBTLEDeviceSupport {
put(XiaomiWeatherService.COMMAND_TYPE, weatherService); put(XiaomiWeatherService.COMMAND_TYPE, weatherService);
put(XiaomiSystemService.COMMAND_TYPE, systemService); put(XiaomiSystemService.COMMAND_TYPE, systemService);
put(XiaomiCalendarService.COMMAND_TYPE, calendarService); put(XiaomiCalendarService.COMMAND_TYPE, calendarService);
put(XiaomiWatchfaceService.COMMAND_TYPE, watchfaceService);
}}; }};
public XiaomiSupport() { public XiaomiSupport() {
@ -324,44 +327,24 @@ public abstract class XiaomiSupport extends AbstractBTLEDeviceSupport {
@Override @Override
public void onInstallApp(final Uri uri) { public void onInstallApp(final Uri uri) {
// TODO watchfaceService.installWatchface(uri);
super.onInstallApp(uri);
} }
@Override @Override
public void onAppInfoReq() { public void onAppInfoReq() {
// TODO watchfaceService.requestWatchfaceList();
super.onAppInfoReq();
} }
@Override @Override
public void onAppStart(final UUID uuid, boolean start) { public void onAppStart(final UUID uuid, boolean start) {
// TODO if (start) {
super.onAppStart(uuid, start); watchfaceService.setWatchface(uuid);
} }
@Override
public void onAppDownload(final UUID uuid) {
// TODO
super.onAppDownload(uuid);
} }
@Override @Override
public void onAppDelete(final UUID uuid) { public void onAppDelete(final UUID uuid) {
// TODO watchfaceService.deleteWatchface(uuid);
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);
} }
@Override @Override
@ -369,12 +352,6 @@ public abstract class XiaomiSupport extends AbstractBTLEDeviceSupport {
healthService.onFetchRecordedData(dataTypes); healthService.onFetchRecordedData(dataTypes);
} }
@Override
public void onReset(final int flags) {
// TODO
super.onReset(flags);
}
@Override @Override
public void onHeartRateTest() { public void onHeartRateTest() {
healthService.onHeartRateTest(); healthService.onHeartRateTest();

View File

@ -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 <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;
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<GBDeviceApp> 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
}
}

View File

@ -11,6 +11,7 @@ message Command {
optional Auth auth = 3; optional Auth auth = 3;
optional System system = 4; optional System system = 4;
optional Watchface watchface = 6;
optional Health health = 10; optional Health health = 10;
optional Calendar calendar = 14; optional Calendar calendar = 14;
optional Music music = 20; optional Music music = 20;
@ -277,6 +278,49 @@ message Charger {
optional uint32 state = 1; // 1 charging, 2 not charging 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 // Health
// //
@ -706,7 +750,7 @@ message DataUpload {
} }
message DataUploadRequest { 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 bytes md5sum = 2;
optional uint32 size = 3; optional uint32 size = 3;
} }