huawei: Implement watchface management

* added watchface management code to HuaweiWatchfaceManager
* HuaweiWatchfaceManager moved from coordinator to support
This commit is contained in:
Vitaliy Tomin 2024-04-07 21:48:44 +08:00
parent ef3654b7e3
commit c206b8f06f
15 changed files with 319 additions and 43 deletions

View File

@ -33,6 +33,7 @@ import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AppManagerActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLClassicDeviceCoordinator;
@ -170,12 +171,21 @@ public abstract class HuaweiBRCoordinator extends AbstractBLClassicDeviceCoordin
@Override
public Class<? extends Activity> getAppsManagementActivity() {
return null;
return AppManagerActivity.class;
}
@Override
public boolean supportsAppListFetching() {
return true;
}
@Override
public boolean supportsAppsManagement(GBDevice device) {
return false;
return true;
}
@Override
public boolean supportsWatchfaceManagement(GBDevice device) {
return true;
}
@Override

View File

@ -34,8 +34,8 @@ import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpec
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Notifications;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Notifications.NotificationConstraintsType;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Watchface;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiWatchfaceManager;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.*;
@ -50,9 +50,11 @@ public class HuaweiCoordinator {
byte notificationCapabilities = -0x01;
ByteBuffer notificationConstraints = null;
private Watchface.WatchfaceDeviceParams watchfaceDeviceParams;
private final HuaweiCoordinatorSupplier parent;
protected HuaweiWatchfaceManager huaweiWatchfaceManager = new HuaweiWatchfaceManager();
private boolean transactionCrypted=true;
public HuaweiCoordinator(HuaweiCoordinatorSupplier parent) {
@ -78,9 +80,6 @@ public class HuaweiCoordinator {
}
}
public HuaweiWatchfaceManager getHuaweiWatchfaceManager(){
return huaweiWatchfaceManager;
}
private SharedPreferences getCapabilitiesSharedPreferences() {
return GBApplication.getContext().getSharedPreferences("huawei_coordinator_capatilities" + parent.getDeviceType().name(), Context.MODE_PRIVATE);
@ -549,5 +548,18 @@ public class HuaweiCoordinator {
"zh_CN",
"zh_TW",
};
}
public short getWidth() {
return watchfaceDeviceParams.width;
}
public short getHeight() {
return watchfaceDeviceParams.height;
}
public void setWatchfaceDeviceParams(Watchface.WatchfaceDeviceParams watchfaceDeviceParams) {
this.watchfaceDeviceParams = watchfaceDeviceParams;
}
}

View File

@ -77,8 +77,8 @@ public class HuaweiInstallHandler implements InstallHandler {
HuaweiWatchfaceManager.WatchfaceDescription description = new HuaweiWatchfaceManager.WatchfaceDescription(watchfaceDescription);
HuaweiWatchfaceManager.Resolution resolution = new HuaweiWatchfaceManager.Resolution();
String deviceScreen = String.format("%d*%d",huaweiCoordinatorSupplier.getHuaweiCoordinator().getHuaweiWatchfaceManager().getHeight(),
huaweiCoordinatorSupplier.getHuaweiCoordinator().getHuaweiWatchfaceManager().getWidth());
String deviceScreen = String.format("%d*%d",huaweiCoordinatorSupplier.getHuaweiCoordinator().getHeight(),
huaweiCoordinatorSupplier.getHuaweiCoordinator().getWidth());
this.valid = resolution.isValid(description.screen, deviceScreen);
installActivity.setInstallEnabled(true);

View File

@ -32,6 +32,7 @@ import java.util.List;
import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AppManagerActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
@ -171,12 +172,21 @@ public abstract class HuaweiLECoordinator extends AbstractBLEDeviceCoordinator i
@Override
public Class<? extends Activity> getAppsManagementActivity() {
return null;
return AppManagerActivity.class;
}
@Override
public boolean supportsAppListFetching() {
return true;
}
@Override
public boolean supportsAppsManagement(GBDevice device) {
return false;
return true;
}
@Override
public boolean supportsWatchfaceManagement(GBDevice device) {
return true;
}
@Override

View File

@ -95,7 +95,7 @@ public class FileUpload {
this.commandId = id;
this.tlv = new HuaweiTLV()
.put(0x7f, 0x000186A0) //ok
.put(0x01, (byte) 0x01); // filetype 1 - watchface, 2 -music
.put(0x01, (byte) 0x01); // filetype 1 - watchface, 2 -music, 3 - png image for watchface background, 7 - app
if (noEncryption == 1)
this.tlv.put(0x09, (byte)0x01); // need on devices which generally encrypted, but files
this.complete = true;

View File

@ -47,6 +47,35 @@ public class Watchface {
throw new RuntimeException(e);
}
}
public boolean isCurrent() {
return (this.type & 1) == 1;
}
public boolean isFactory() {
return ((this.type >> 1 )& 1) == 1;
}
public boolean isEditable() {
return ((this.type >> 3 )& 1) == 1;
}
public boolean isVideo() {
return ((this.type >> 4 )& 1) == 1;
}
public boolean isPhoto() {
return ((this.type >> 5 )& 1) == 1;
}
public boolean isTryout() {
return ((this.type >> 6 )& 1) == 1;
}
public boolean isKaleidoskop() {
return ((this.type >> 7 )& 1) == 1;
}
}
@ -151,15 +180,18 @@ public class Watchface {
public static class WatchfaceOperation {
public static final byte id = 0x03;
public static final byte operationActive = 1;
public static final byte operationDelete = 2;
public static class Request extends HuaweiPacket {
public Request(ParamsProvider paramsProvider,
String fileName) {
String fileName, byte operation) {
super(paramsProvider);
this.serviceId = Watchface.id;
this.tlv = new HuaweiTLV()
.put(0x01, fileName.split("_")[0])
.put(0x02, fileName.split("_")[1])
.put(0x03, (byte) 0x01);
.put(0x03, operation);
this.commandId = id;
}

View File

@ -437,7 +437,7 @@ public class AsynchronousResponse {
support.huaweiUploadManager.unsetDeviceBusy();
support.onUploadProgress(R.string.updatefirmwareoperation_update_complete, 100, false);
SendFileUploadComplete sendFileUploadComplete = new SendFileUploadComplete(this.support);
SendWatchfaceOperation sendWatchfaceOperation = new SendWatchfaceOperation(this.support, this.support.huaweiUploadManager.getFileName());
SendWatchfaceOperation sendWatchfaceOperation = new SendWatchfaceOperation(this.support, this.support.huaweiUploadManager.getFileName(), Watchface.WatchfaceOperation.operationActive);
sendFileUploadComplete.doPerform();
sendWatchfaceOperation.doPerform();

View File

@ -22,6 +22,7 @@ import android.net.Uri;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
@ -136,4 +137,22 @@ public class HuaweiBRSupport extends AbstractBTBRDeviceSupport {
public void onInstallApp(Uri uri) {
supportProvider.onInstallApp(uri);
}
@Override
public void onAppInfoReq() {
supportProvider.onAppInfoReq();
}
@Override
public void onAppStart(final UUID uuid, boolean start) {
if (start) {
supportProvider.onAppStart(uuid, start);
}
}
@Override
public void onAppDelete(final UUID uuid) {
supportProvider.onAppDelete(uuid);
}
}

View File

@ -26,6 +26,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
@ -144,4 +145,23 @@ public class HuaweiLESupport extends AbstractBTLEDeviceSupport {
public void onInstallApp(Uri uri) {
supportProvider.onInstallApp(uri);
}
@Override
public void onAppInfoReq() {
supportProvider.onAppInfoReq();
}
@Override
public void onAppStart(final UUID uuid, boolean start) {
if (start) {
supportProvider.onAppStart(uuid, start);
}
}
@Override
public void onAppDelete(final UUID uuid) {
supportProvider.onAppDelete(uuid);
}
}

View File

@ -186,6 +186,8 @@ public class HuaweiSupportProvider {
protected ResponseManager responseManager = new ResponseManager(this);
protected HuaweiUploadManager huaweiUploadManager = new HuaweiUploadManager(this);
protected HuaweiWatchfaceManager huaweiWatchfaceManager = new HuaweiWatchfaceManager(this);
public HuaweiCoordinatorSupplier getCoordinator() {
return ((HuaweiCoordinatorSupplier) this.gbDevice.getDeviceCoordinator());
}
@ -193,6 +195,9 @@ public class HuaweiSupportProvider {
public HuaweiCoordinator getHuaweiCoordinator() {
return getCoordinator().getHuaweiCoordinator();
}
public HuaweiWatchfaceManager getHuaweiWatchfaceManager() {
return huaweiWatchfaceManager;
}
public HuaweiSupportProvider(HuaweiBRSupport support) {
this.brSupport = support;
@ -736,13 +741,6 @@ public class HuaweiSupportProvider {
if (getHuaweiCoordinator().supportsWatchfaceParams()) {
GetWatchfaceParams getWatchfaceParams = new GetWatchfaceParams(this);
getWatchfaceParams.doPerform();
GetWatchfacesList getWatchfacesList = new GetWatchfacesList(this);
getWatchfacesList.doPerform();
GetWatchfacesNames getWatchfacesNames = new GetWatchfacesNames(this,
getHuaweiCoordinator().getHuaweiWatchfaceManager().getInstalledWatchfaceInfoList());
getWatchfacesNames.doPerform();
}
} catch (IOException e) {
GB.toast(getContext(), "Initialize dynamic services of Huawei device failed", Toast.LENGTH_SHORT, GB.ERROR,
@ -1844,14 +1842,11 @@ public class HuaweiSupportProvider {
public void onInstallApp(Uri uri) {
LOG.info("enter onAppInstall uri: "+uri);
huaweiUploadManager.setFileName(getHuaweiCoordinator().getHuaweiWatchfaceManager().getRandomName());
huaweiUploadManager.setFileName(huaweiWatchfaceManager.getRandomName());
huaweiUploadManager.setWatchfaceUri(uri);
SendFileUploadInfo sendFileUploadInfo = new SendFileUploadInfo(this, huaweiUploadManager);
try {
SendFileUploadInfo sendFileUploadInfo = new SendFileUploadInfo(this, huaweiUploadManager);
sendFileUploadInfo.doPerform();
} catch (IOException e) {
GB.toast(context, "Failed to send watchface info", Toast.LENGTH_SHORT, GB.ERROR, e);
@ -1886,5 +1881,18 @@ public class HuaweiSupportProvider {
}
}
public void onAppInfoReq() {
huaweiWatchfaceManager.requestWatchfaceList();
}
public void onAppStart(final UUID uuid, boolean start) {
if (start) {
huaweiWatchfaceManager.setWatchface(uuid);
}
}
public void onAppDelete(final UUID uuid) {
huaweiWatchfaceManager.deleteWatchface(uuid);
}
}

View File

@ -1,23 +1,39 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Watchface;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Watchface.WatchfaceDeviceParams;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetWatchfacesList;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetWatchfacesNames;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.Request;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWatchfaceOperation;
public class HuaweiWatchfaceManager
{
Logger LOG = LoggerFactory.getLogger(HuaweiCoordinator.class);
public static class Resolution {
@ -86,15 +102,14 @@ public class HuaweiWatchfaceManager
}
private WatchfaceDeviceParams params;
private List<Watchface.InstalledWatchfaceInfo> installedWatchfaceInfoList;
private HashMap<String, String> watchfacesNames;
public HuaweiWatchfaceManager() {
private HuaweiSupportProvider support;
}
public void setParams(WatchfaceDeviceParams params) {
this.params = params;
public HuaweiWatchfaceManager(HuaweiSupportProvider support) {
this.support = support;
}
public void setInstalledWatchfaceInfoList(List<Watchface.InstalledWatchfaceInfo> list) {
@ -111,13 +126,7 @@ public class HuaweiWatchfaceManager
this.watchfacesNames = map;
}
public short getWidth() {
return params.width;
}
public short getHeight() {
return params.height;
}
public String getRandomName() {
Random random = new Random();
@ -132,5 +141,159 @@ public class HuaweiWatchfaceManager
return res;
}
public static UUID toWatchfaceUUID(final String id) {
// Watchface IDs are numbers as strings - pad them to the right with F
// and encode as UUID
final String padded = String.format("%-32s", id).replace(' ', 'F');
return UUID.fromString(
padded.substring(0, 8) + "-" +
padded.substring(8, 12) + "-" +
padded.substring(12, 16) + "-" +
padded.substring(16, 20) + "-" +
padded.substring(20, 32)
);
}
public static String toWatchfaceId(final UUID uuid) {
return uuid.toString()
.replaceAll("-", "")
.replaceAll("f", "")
.replaceAll("F", "");
}
public void handleWatchfaceList() {
final List<GBDeviceApp> gbDeviceApps = new ArrayList<>();
for (final Watchface.InstalledWatchfaceInfo watchfaceInfo : installedWatchfaceInfoList) {
final UUID uuid = toWatchfaceUUID(watchfaceInfo.fileName);
GBDeviceApp gbDeviceApp = new GBDeviceApp(
uuid,
watchfacesNames.get(watchfaceInfo.fileName),
"",
"",
GBDeviceApp.Type.WATCHFACE
);
gbDeviceApps.add(gbDeviceApp);
}
final GBDeviceEventAppInfo appInfoCmd = new GBDeviceEventAppInfo();
appInfoCmd.apps = gbDeviceApps.toArray(new GBDeviceApp[0]);
support.evaluateGBDeviceEvent(appInfoCmd);
}
public void updateWatchfaceNames() {
Request.RequestCallback finalizeReq = new Request.RequestCallback() {
@Override
public void call() {
handleWatchfaceList();
}
@Override
public void handleException(Request.ResponseParseException e) {
LOG.error("Watchface update list exception", e);
}
};
try {
GetWatchfacesNames getWatchfacesNames = new GetWatchfacesNames(support, installedWatchfaceInfoList);
getWatchfacesNames.setFinalizeReq(finalizeReq);
getWatchfacesNames.doPerform();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void requestWatchfaceList() {
Request.RequestCallback finalizeReq = new Request.RequestCallback() {
@Override
public void call() {
updateWatchfaceNames();
}
@Override
public void handleException(Request.ResponseParseException e) {
LOG.error("Watchface update list exception", e);
}
};
try {
GetWatchfacesList getWatchfacesList = new GetWatchfacesList(support);
getWatchfacesList.setFinalizeReq(finalizeReq);
getWatchfacesList.doPerform();
} catch (IOException e) {
throw new RuntimeException(e);
}
};
public void setWatchface(UUID uuid) {
Request.RequestCallback finalizeReq = new Request.RequestCallback() {
@Override
public void call() {
requestWatchfaceList();
}
@Override
public void handleException(Request.ResponseParseException e) {
LOG.error("Watchface update list exception", e);
}
};
try {
SendWatchfaceOperation sendWatchfaceOperation = new SendWatchfaceOperation(support,
getFullFileName(uuid),
Watchface.WatchfaceOperation.operationActive);
sendWatchfaceOperation.setFinalizeReq(finalizeReq);
sendWatchfaceOperation.doPerform();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void deleteWatchface(UUID uuid) {
Request.RequestCallback finalizeReq = new Request.RequestCallback() {
@Override
public void call() {
requestWatchfaceList();
}
@Override
public void handleException(Request.ResponseParseException e) {
LOG.error("Watchface update list exception", e);
}
};
try {
SendWatchfaceOperation sendWatchfaceOperation = new SendWatchfaceOperation(support,
getFullFileName(uuid),
Watchface.WatchfaceOperation.operationDelete);
sendWatchfaceOperation.setFinalizeReq(finalizeReq);
sendWatchfaceOperation.doPerform();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private String getFullFileName(UUID uuid) {
String name = toWatchfaceId(uuid);
String version = "";
for (final Watchface.InstalledWatchfaceInfo watchfaceInfo : installedWatchfaceInfoList) {
if (watchfaceInfo.fileName.equals(name)) {
version = watchfaceInfo.version;
break;
}
}
String filename = name + "_" + version;
return filename;
}
}

View File

@ -35,6 +35,6 @@ public class GetWatchfaceParams extends Request{
throw new ResponseTypeMismatchException(receivedPacket, Watchface.WatchfaceParams.Response.class);
Watchface.WatchfaceParams.Response resp = (Watchface.WatchfaceParams.Response)(receivedPacket);
supportProvider.getHuaweiCoordinator().getHuaweiWatchfaceManager().setParams(resp.params);
supportProvider.getHuaweiCoordinator().setWatchfaceDeviceParams(resp.params);
}
}

View File

@ -35,6 +35,6 @@ public class GetWatchfacesList extends Request{
throw new ResponseTypeMismatchException(receivedPacket, Watchface.DeviceWatchInfo.Response.class);
Watchface.DeviceWatchInfo.Response resp = (Watchface.DeviceWatchInfo.Response)(receivedPacket);
supportProvider.getHuaweiCoordinator().getHuaweiWatchfaceManager().setInstalledWatchfaceInfoList(resp.watchfaceInfoList);
supportProvider.getHuaweiWatchfaceManager().setInstalledWatchfaceInfoList(resp.watchfaceInfoList);
}
}

View File

@ -38,6 +38,6 @@ public class GetWatchfacesNames extends Request{
throw new ResponseTypeMismatchException(receivedPacket, Watchface.WatchfaceNameInfo.Response.class);
Watchface.WatchfaceNameInfo.Response resp = (Watchface.WatchfaceNameInfo.Response)(receivedPacket);
supportProvider.getHuaweiCoordinator().getHuaweiWatchfaceManager().setWatchfacesNames(resp.watchFaceNames);
supportProvider.getHuaweiWatchfaceManager().setWatchfacesNames(resp.watchFaceNames);
}
}

View File

@ -8,17 +8,19 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupport
public class SendWatchfaceOperation extends Request {
private String fileName;
public SendWatchfaceOperation(HuaweiSupportProvider support, String filename) {
private byte operation;
public SendWatchfaceOperation(HuaweiSupportProvider support, String filename, byte operation) {
super(support);
this.serviceId = Watchface.id;
this.commandId = Watchface.WatchfaceOperation.id;
this.fileName = filename;
this.operation = operation;
}
@Override
protected List<byte[]> createRequest() throws RequestCreationException {
try {
return new Watchface.WatchfaceOperation.Request(this.paramsProvider, this.fileName ).serialize();
return new Watchface.WatchfaceOperation.Request(this.paramsProvider, this.fileName, this.operation ).serialize();
} catch (HuaweiPacket.CryptoException e) {
throw new RequestCreationException(e);
}