Huawei: watchface upload - SendWatchfaceInfo

* Added HuaweiWatchfaceManager to store current watchface info
* implemented SendWatchfaceInfo request
* added code to handle onInstallApp for BRCoordinator and LECoordinator
This commit is contained in:
Vitaliy Tomin 2024-03-31 18:52:31 +08:00
parent 054e8e18ee
commit 8e71487092
10 changed files with 241 additions and 22 deletions

View File

@ -208,9 +208,11 @@ public abstract class HuaweiBRCoordinator extends AbstractBLClassicDeviceCoordin
return huaweiCoordinator.supportsMusic();
}
@Override
public InstallHandler findInstallHandler(Uri uri, Context context) {
return null;
HuaweiInstallHandler handler = new HuaweiInstallHandler(uri, context);
return handler.isValid() ? handler : null;
}
@Override

View File

@ -5,24 +5,19 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import com.google.gson.Gson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.GBZipFile;
import nodomain.freeyourgadget.gadgetbridge.util.UriHelper;
import nodomain.freeyourgadget.gadgetbridge.util.ZipFileException;
@ -40,14 +35,11 @@ public class HuaweiInstallHandler implements InstallHandler {
UriHelper uriHelper;
try {
uriHelper = UriHelper.get(uri, this.context);
GBZipFile watchfacePackage = new GBZipFile(uriHelper.openInputStream());
String watchfaceDescription = new String(watchfacePackage.getFileFromZip("description.xml"));
watchfaceBin = watchfacePackage.getFileFromZip("com.huawei.watchface");
final byte[] preview = watchfacePackage.getFileFromZip("preview/cover.jpg");
previewbMap = BitmapFactory.decodeByteArray(preview, 0, preview.length);
@ -65,17 +57,6 @@ public class HuaweiInstallHandler implements InstallHandler {
return;
}
try {
MessageDigest m = MessageDigest.getInstance("SHA256");
m.update(watchfaceBin, 0, watchfaceBin.length);
watchfaceSHA256 = m.digest();
} catch (NoSuchAlgorithmException e) {
LOG.error("Digest alghoritm not found.", e);
return;
}
LOG.info("watchface loaded, SHA256: "+ GB.hexdump(watchfaceSHA256));
}
@Override
@ -97,7 +78,7 @@ public class HuaweiInstallHandler implements InstallHandler {
return;
}
if (device.getType() != DeviceType.HUAWEIBAND7 || !device.isConnected()) { //FIXME: Add all tested huawei devices?
if ( !device.isConnected()) { //FIXME: Add all tested huawei devices?
LOG.error("Firmware cannot be installed (not connected or wrong device)");
installActivity.setInfoText("Firmware cannot be installed (not connected or wrong device)");
installActivity.setInstallEnabled(false);

View File

@ -213,7 +213,6 @@ public abstract class HuaweiLECoordinator extends AbstractBLEDeviceCoordinator i
public InstallHandler findInstallHandler(Uri uri, Context context) {
HuaweiInstallHandler handler = new HuaweiInstallHandler(uri, context);
return handler.isValid() ? handler : null;
}
@Override

View File

@ -0,0 +1,73 @@
package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
public class WatchfaceUpload {
public static final byte id = 0x28;
public static class WatchfaceStartSend {
public static final byte id = 0x02;
public static class Request extends HuaweiPacket {
public Request(ParamsProvider paramsProvider,
int fileSize,
String watchfaceName,
String watchfaceVersion) {
super(paramsProvider);
this.serviceId = WatchfaceUpload.id;
this.commandId = id;
String filename = watchfaceName + "_" + watchfaceVersion;
this.tlv = new HuaweiTLV()
.put(0x01, filename)
.put(0x02, fileSize)
.put(0x03, (byte) 0x1) // ???
.put(0x05, watchfaceName)
.put(0x06, watchfaceVersion);
this.complete = true;
}
public static class Response extends HuaweiPacket {
public Response (ParamsProvider paramsProvider) {
super(paramsProvider);
}
}
}
}
public static class WatchfaceSendHash {
public static final byte id = 0x03;
public static class Request extends HuaweiPacket {
public Request(ParamsProvider paramsProvider,
byte[] hash) {
super(paramsProvider);
this.serviceId = WatchfaceUpload.id;
this.commandId = id;
this.tlv = new HuaweiTLV()
.put(0x01, (byte) 1) //???
.put(0x03, hash);
this.complete = true;
}
public static class Response extends HuaweiPacket {
public Response (ParamsProvider paramsProvider) {
super(paramsProvider);
}
}
}
}
}

View File

@ -48,6 +48,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FindPhone;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.GpsAndTime;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Menstrual;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.MusicControl;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.WatchfaceUpload;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.Request;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetPhoneInfoRequest;
@ -100,6 +101,7 @@ public class AsynchronousResponse {
handleMenstrualModifyTime(response);
handleWeatherCheck(response);
handleGpsRequest(response);
handleWatchfaceHash(response);
} catch (Request.ResponseParseException e) {
LOG.error("Response parse exception", e);
}
@ -384,6 +386,12 @@ public class AsynchronousResponse {
}
}
private void handleWatchfaceHash(HuaweiPacket response) {
if (response.serviceId == WatchfaceUpload.id && response.commandId == WatchfaceUpload.WatchfaceSendHash.id) {
}
}
private void handleWeatherCheck(HuaweiPacket response) {
if (response.serviceId == Weather.id && response.commandId == 0x04) {
// Send back ok

View File

@ -17,6 +17,7 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei;
import android.location.Location;
import android.net.Uri;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -130,4 +131,9 @@ public class HuaweiBRSupport extends AbstractBTBRDeviceSupport {
public void onSetGpsLocation(Location location) {
supportProvider.onSetGpsLocation(location);
}
@Override
public void onInstallApp(Uri uri) {
supportProvider.onInstallApp(uri);
}
}

View File

@ -19,6 +19,8 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.location.Location;
import android.net.Uri;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -137,4 +139,9 @@ public class HuaweiLESupport extends AbstractBTLEDeviceSupport {
public void onSetGpsLocation(Location location) {
supportProvider.onSetGpsLocation(location);
}
@Override
public void onInstallApp(Uri uri) {
supportProvider.onInstallApp(uri);
}
}

View File

@ -20,6 +20,7 @@ import android.bluetooth.BluetoothGattCharacteristic;
import android.content.Context;
import android.content.SharedPreferences;
import android.location.Location;
import android.net.Uri;
import android.widget.Toast;
import androidx.annotation.NonNull;
@ -87,6 +88,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetS
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendExtendedAccountRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendGpsAndTimeToDeviceRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendGpsDataRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWatchfaceInfo;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWeatherCurrentRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendNotifyHeartRateCapabilityRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendNotifyRestHeartRateCapabilityRequest;
@ -178,6 +180,7 @@ public class HuaweiSupportProvider {
private final HuaweiPacket.ParamsProvider paramsProvider = new HuaweiPacket.ParamsProvider();
protected ResponseManager responseManager = new ResponseManager(this);
protected HuaweiWatchfaceManager huaweiWatchfaceManager = new HuaweiWatchfaceManager(this);
public HuaweiCoordinatorSupplier getCoordinator() {
return ((HuaweiCoordinatorSupplier) this.gbDevice.getDeviceCoordinator());
@ -1822,4 +1825,23 @@ public class HuaweiSupportProvider {
LOG.error("Failed to send GPS data", e);
}
}
public void onInstallApp(Uri uri) {
LOG.info("enter onAppInstall uri: "+uri);
huaweiWatchfaceManager.setWatchfaceUri(uri);
SendWatchfaceInfo sendWatchfaceInfo = new SendWatchfaceInfo(this, huaweiWatchfaceManager);
try {
sendWatchfaceInfo.doPerform();
} catch (IOException e) {
GB.toast(context, "Failed to send watchface info", Toast.LENGTH_SHORT, GB.ERROR, e);
LOG.error("Failed to send watchface info", e);
}
}
}

View File

@ -0,0 +1,85 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import android.net.Uri;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.GBZipFile;
import nodomain.freeyourgadget.gadgetbridge.util.UriHelper;
import nodomain.freeyourgadget.gadgetbridge.util.ZipFileException;
public class HuaweiWatchfaceManager {
private static final Logger LOG = LoggerFactory.getLogger(HuaweiWatchfaceManager.class);
private final HuaweiSupportProvider support;
byte[] watchfaceBin;
byte[] watchfaceSHA256;
int fileSize = 0;
String watchfaceName = "413493857";
String watchfaceVersion = "1.0.0";
public HuaweiWatchfaceManager(HuaweiSupportProvider support) {
this.support=support;
}
public void setWatchfaceUri(Uri uri) {
UriHelper uriHelper;
try {
uriHelper = UriHelper.get(uri, support.getContext());
GBZipFile watchfacePackage = new GBZipFile(uriHelper.openInputStream());
String watchfaceDescription = new String(watchfacePackage.getFileFromZip("description.xml"));
watchfaceBin = watchfacePackage.getFileFromZip("com.huawei.watchface");
fileSize = watchfaceBin.length;
} catch (ZipFileException e) {
LOG.error("Unable to read watchface file.", e);
return;
} catch (FileNotFoundException e) {
LOG.error("The watchface file was not found.", e);
return;
} catch (IOException e) {
LOG.error("General IO error occurred.", e);
return;
} catch (Exception e) {
LOG.error("Unknown error occurred.", e);
return;
}
try {
MessageDigest m = MessageDigest.getInstance("SHA256");
m.update(watchfaceBin, 0, watchfaceBin.length);
watchfaceSHA256 = m.digest();
} catch (NoSuchAlgorithmException e) {
LOG.error("Digest alghoritm not found.", e);
return;
}
//TODO: generate random watchfaceName and watchfaceVersion
LOG.info("watchface loaded, SHA256: "+ GB.hexdump(watchfaceSHA256) + " watchfaceName: " + watchfaceName + " watchfaceVersion: "+watchfaceVersion);
}
public int getFileSize() {
return fileSize;
}
public String getWatchfaceName() {
return watchfaceName;
}
public String getWatchfaceVersion() {
return watchfaceVersion;
}
}

View File

@ -0,0 +1,36 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.WatchfaceUpload;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiWatchfaceManager;
public class SendWatchfaceInfo extends Request{
HuaweiWatchfaceManager huaweiWatchfaceManager;
public SendWatchfaceInfo(HuaweiSupportProvider support,
HuaweiWatchfaceManager huaweiWatchfaceManager) {
super(support);
this.huaweiWatchfaceManager = huaweiWatchfaceManager;
this.serviceId = WatchfaceUpload.id;
this.commandId = WatchfaceUpload.WatchfaceStartSend.id;
}
@Override
protected List<byte[]> createRequest() throws RequestCreationException {
try {
return new WatchfaceUpload.WatchfaceStartSend.Request(this.paramsProvider,
huaweiWatchfaceManager.getFileSize(),
huaweiWatchfaceManager.getWatchfaceName(),
huaweiWatchfaceManager.getWatchfaceVersion()
).serialize();
} catch (HuaweiPacket.CryptoException e) {
throw new RequestCreationException(e);
}
}
}