From e54fd95a8be8a13966d06859027e8e3035b75074 Mon Sep 17 00:00:00 2001 From: Me7c7 Date: Thu, 29 Aug 2024 17:23:42 +0300 Subject: [PATCH] Huawei: Basic support for the installation of the applications --- .../devices/huawei/HuaweiAppParser.java | 65 +++++++ .../devices/huawei/HuaweiCoordinator.java | 20 ++- .../devices/huawei/HuaweiInstallHandler.java | 138 ++++++++++----- .../devices/huawei/HuaweiPacket.java | 11 ++ .../devices/huawei/packets/App.java | 139 +++++++++++++++ .../devices/huawei/packets/FileUpload.java | 3 +- .../devices/huawei/AsynchronousResponse.java | 18 ++ .../devices/huawei/HuaweiAppManager.java | 162 ++++++++++++++++++ .../devices/huawei/HuaweiFwHelper.java | 71 +++++++- .../devices/huawei/HuaweiSupportProvider.java | 48 +++++- .../devices/huawei/HuaweiUploadManager.java | 7 +- .../huawei/HuaweiWatchfaceManager.java | 9 +- .../huawei/requests/GetAppInfoParams.java | 35 ++++ .../devices/huawei/requests/GetAppNames.java | 33 ++++ .../huawei/requests/SendAppDelete.java | 28 +++ 15 files changed, 725 insertions(+), 62 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiAppParser.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/App.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiAppManager.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetAppInfoParams.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetAppNames.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendAppDelete.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiAppParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiAppParser.java new file mode 100644 index 000000000..56536be44 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiAppParser.java @@ -0,0 +1,65 @@ +package nodomain.freeyourgadget.gadgetbridge.devices.huawei; + +import java.nio.ByteBuffer; +import java.util.ArrayList; + +public class HuaweiAppParser { + + public static class AppFileEntry { + public String filename; + public String folder; + public String unknown; + public byte[] content; + } + + private String packageName; + private final ArrayList entries = new ArrayList<>(); + + public String getPackageName() { + return packageName; + } + + public byte[] getEntryContent(String filename) { + for(AppFileEntry en: entries) { + if(en.filename.equals(filename)) { + return en.content; + } + } + return null; + } + + private byte[] readData(ByteBuffer data) { + int len = data.getInt(); + byte[] newPayload = new byte[len]; + data.get(newPayload, 0, len); + return newPayload; + } + + private String readString(ByteBuffer data) { + return new String(readData(data)); + } + + public void parseData(byte[] in) throws Exception { + + ByteBuffer data = ByteBuffer.wrap(in); + + byte magic = data.get(); + if(magic != (byte)0xbe){ + throw new Exception("Invalid magic"); + } + this.packageName = readString(data); + while(data.remaining() > 0) { + int pos = data.position(); + byte a = data.get(); + if(a != 0x00) + break; + data.position(pos); + AppFileEntry ent = new AppFileEntry(); + ent.filename = readString(data); + ent.folder = readString(data); + ent.unknown = readString(data); + ent.content = readData(data); + entries.add(ent); + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCoordinator.java index 4224fef90..70c49c4f4 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCoordinator.java @@ -22,7 +22,6 @@ import android.content.SharedPreferences; import android.net.Uri; import java.nio.ByteBuffer; -import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.TreeMap; @@ -37,6 +36,7 @@ import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AppManagerActi import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.App; 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; @@ -59,6 +59,8 @@ public class HuaweiCoordinator { private Watchface.WatchfaceDeviceParams watchfaceDeviceParams; + private App.AppDeviceParams appDeviceParams; + private final HuaweiCoordinatorSupplier parent; private boolean transactionCrypted=true; @@ -405,6 +407,8 @@ public class HuaweiCoordinator { public boolean supportsWatchfaceParams(){ return supportsCommandForService(0x27, 0x01);} + public boolean supportsAppParams(){ return supportsCommandForService(0x2a, 0x06);} + public boolean supportsWeather() { return supportsCommandForService(0x0f, 0x01); } @@ -584,20 +588,26 @@ public class HuaweiCoordinator { this.watchfaceDeviceParams = watchfaceDeviceParams; } + public void setAppDeviceParams(App.AppDeviceParams appDeviceParams) { + this.appDeviceParams = appDeviceParams; + } + + public App.AppDeviceParams getAppDeviceParams() { + return appDeviceParams; + } + public Class getAppManagerActivity() { return AppManagerActivity.class; } - public boolean getSupportsAppListFetching() { - return true; - } + public boolean getSupportsAppListFetching() { return true; } public boolean getSupportsAppsManagement(GBDevice device) { return true; } public boolean getSupportsInstalledAppManagement(GBDevice device) { - return false; + return this.supportsAppParams(); // NOTE: this check can be incorrect. But looks like it works } public boolean getSupportsCachedAppManagement(GBDevice device) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiInstallHandler.java index 78dc64898..2e9fa28b2 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiInstallHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiInstallHandler.java @@ -29,6 +29,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.GenericItem; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiAppManager; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiFwHelper; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiWatchfaceManager; @@ -56,57 +57,108 @@ public class HuaweiInstallHandler implements InstallHandler { return; } - final HuaweiCoordinatorSupplier huaweiCoordinatorSupplier = (HuaweiCoordinatorSupplier) coordinator; + if(helper.isWatchface()) { + final HuaweiCoordinatorSupplier huaweiCoordinatorSupplier = (HuaweiCoordinatorSupplier) coordinator; - HuaweiWatchfaceManager.WatchfaceDescription description = helper.getWatchfaceDescription(); + HuaweiWatchfaceManager.WatchfaceDescription description = helper.getWatchfaceDescription(); - HuaweiWatchfaceManager.Resolution resolution = new HuaweiWatchfaceManager.Resolution(); - String deviceScreen = String.format("%d*%d",huaweiCoordinatorSupplier.getHuaweiCoordinator().getHeight(), - huaweiCoordinatorSupplier.getHuaweiCoordinator().getWidth()); - this.valid = resolution.isValid(description.screen, deviceScreen); + HuaweiWatchfaceManager.Resolution resolution = new HuaweiWatchfaceManager.Resolution(); + String deviceScreen = String.format("%d*%d", huaweiCoordinatorSupplier.getHuaweiCoordinator().getHeight(), + huaweiCoordinatorSupplier.getHuaweiCoordinator().getWidth()); + this.valid = resolution.isValid(description.screen, deviceScreen); - installActivity.setInstallEnabled(true); + installActivity.setInstallEnabled(true); - GenericItem installItem = new GenericItem(); + GenericItem installItem = new GenericItem(); - if (helper.getWatchfacePreviewBitmap() != null) { - installItem.setPreview(helper.getWatchfacePreviewBitmap()); + if (helper.getWatchfacePreviewBitmap() != null) { + installItem.setPreview(helper.getWatchfacePreviewBitmap()); + } + + installItem.setName(description.title); + installActivity.setInstallItem(installItem); + if (device.isBusy()) { + LOG.error("Firmware cannot be installed (device busy)"); + installActivity.setInfoText("Firmware cannot be installed (device busy)"); + installActivity.setInfoText(device.getBusyTask()); + installActivity.setInstallEnabled(false); + return; + } + + if (!device.isConnected()) { + 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); + return; + } + + if (!this.valid) { + LOG.error("Watchface cannot be installed"); + installActivity.setInfoText(context.getString(R.string.watchface_resolution_doesnt_match, + resolution.screenByThemeVersion(description.screen), deviceScreen)); + installActivity.setInstallEnabled(false); + return; + } + + installItem.setDetails(description.version); + + installItem.setIcon(R.drawable.ic_watchface); + installActivity.setInfoText(context.getString(R.string.watchface_install_info, installItem.getName(), description.version, description.author)); + + LOG.debug("Initialized HuaweiInstallHandler"); + } else if (helper.isAPP()) { + final HuaweiCoordinatorSupplier huaweiCoordinatorSupplier = (HuaweiCoordinatorSupplier) coordinator; + + String screenWindow = String.format("%d*%d", huaweiCoordinatorSupplier.getHuaweiCoordinator().getAppDeviceParams().height, + huaweiCoordinatorSupplier.getHuaweiCoordinator().getAppDeviceParams().width); + + String screenShape = huaweiCoordinatorSupplier.getHuaweiCoordinator().getAppDeviceParams().screenShape; + + HuaweiAppManager.AppConfig config = helper.getAppconfig(); + + this.valid = config.checkDistroFilters(screenShape, screenWindow); + + installActivity.setInstallEnabled(true); + + GenericItem installItem = new GenericItem(); + + if (helper.getWatchfacePreviewBitmap() != null) { + installItem.setPreview(helper.getWatchfacePreviewBitmap()); + } + + installItem.setName(config.bundleName); + installActivity.setInstallItem(installItem); + if (device.isBusy()) { + LOG.error("Firmware cannot be installed (device busy)"); + installActivity.setInfoText("Firmware cannot be installed (device busy)"); + installActivity.setInfoText(device.getBusyTask()); + installActivity.setInstallEnabled(false); + return; + } + + if (!device.isConnected()) { + 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); + return; + } + + if (!this.valid) { + LOG.error("App cannot be installed"); + installActivity.setInstallEnabled(false); + return; + } + + installItem.setDetails(config.version); + + installItem.setIcon(R.drawable.ic_watchapp); + + installActivity.setInfoText(context.getString(R.string.app_install_info, installItem.getName(), config.version, config.vendor)); + + LOG.debug("Initialized HuaweiInstallHandler"); } - installItem.setName(description.title); - installActivity.setInstallItem(installItem); - if (device.isBusy()) { - LOG.error("Firmware cannot be installed (device busy)"); - installActivity.setInfoText("Firmware cannot be installed (device busy)"); - installActivity.setInfoText(device.getBusyTask()); - installActivity.setInstallEnabled(false); - return; - } - - if ( !device.isConnected()) { - 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); - return; - } - - if (!this.valid) { - LOG.error("Watchface cannot be installed"); - installActivity.setInfoText(context.getString(R.string.watchface_resolution_doesnt_match, - resolution.screenByThemeVersion(description.screen), deviceScreen)); - installActivity.setInstallEnabled(false); - return; - } - - - //installItem.setDetails(description.version); - - installItem.setIcon(R.drawable.ic_watchface); - installActivity.setInfoText(context.getString(R.string.watchface_install_info, installItem.getName(), description.version, description.author)); - - LOG.debug("Initialized HuaweiInstallHandler"); - } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiPacket.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiPacket.java index 87a24c6d2..8da9b3ea2 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiPacket.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiPacket.java @@ -31,6 +31,7 @@ import org.slf4j.LoggerFactory; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Alarms; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.AccountRelated; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.App; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Calls; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.CameraRemote; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService0A; @@ -616,6 +617,16 @@ public class HuaweiPacket { this.isEncrypted = this.attemptDecrypt(); // Helps with debugging return this; } + case App.id: + switch (this.commandId) { + case App.AppNames.id: + return new App.AppNames.Response(paramsProvider).fromPacket(this); + case App.AppInfoParams.id: + return new App.AppInfoParams.Response(paramsProvider).fromPacket(this); + default: + this.isEncrypted = this.attemptDecrypt(); // Helps with debugging + return this; + } default: this.isEncrypted = this.attemptDecrypt(); // Helps with debugging return this; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/App.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/App.java new file mode 100644 index 000000000..8e89327b3 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/App.java @@ -0,0 +1,139 @@ +package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets; + +import java.util.ArrayList; +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV; + +public class App { + public static final byte id = 0x2a; + + public static class AppDeviceParams { + public byte unknown1 = 0; + public int unknown2 = 0; + public String osVersion = ""; + public String screenShape = ""; + public int width = 0; + public int height = 0; + public int unknown3 = 0; + public String buildType = ""; + } + + public static class InstalledAppInfo { + public String packageName; + public String version; + public int unknown1; + public String appName; + public int unknown2; + public int versionCode; + public byte unknown4; + public byte unknown6; + + public InstalledAppInfo(HuaweiTLV tlv) throws HuaweiPacket.MissingTagException { + this.packageName = tlv.getString(0x03); + this.version = tlv.getString(0x04); + this.unknown1 = tlv.getInteger(0x05); + this.appName = tlv.getString(0x06); + this.unknown2 = tlv.getInteger(0x07); + this.versionCode = tlv.getInteger(0x09); + this.unknown4 = tlv.getByte(0x0a); + //{tag: b - Value: } - + this.unknown6 = tlv.getByte(0x0d); + } + } + + public static class AppDelete { + public static final byte id = 0x01; + + public static class Request extends HuaweiPacket { + public Request(ParamsProvider paramsProvider, + String packageName) { + super(paramsProvider); + this.serviceId = App.id; + this.commandId = id; + this.tlv = new HuaweiTLV() + .put(0x01, (byte)1) + .put(0x02, packageName); + } + } + + public static class Response extends HuaweiPacket { + public Response (ParamsProvider paramsProvider) { + super(paramsProvider); + } + } + } + + public static class AppNames { + public static final byte id = 0x03; + + public static class Request extends HuaweiPacket { + public Request(ParamsProvider paramsProvider) { + super(paramsProvider); + this.serviceId = App.id; + this.commandId = id; + this.tlv = new HuaweiTLV() + .put(0x81); + } + } + + public static class Response extends HuaweiPacket { + + public List appInfoList; + public Response (ParamsProvider paramsProvider) { + super(paramsProvider); + } + + @Override + public void parseTlv() throws HuaweiPacket.ParseException { + appInfoList = new ArrayList<>(); + if(this.tlv.contains(0x81)) { + for (HuaweiTLV subTlv : this.tlv.getObject(0x81).getObjects(0x82)) { + appInfoList.add(new InstalledAppInfo(subTlv)); + } + } + } + } + } + + public static class AppInfoParams { + public static final byte id = 0x06; + public static class Request extends HuaweiPacket { + + public Request(ParamsProvider paramsProvider) { + super(paramsProvider); + this.serviceId = App.id; + this.commandId = id; + + this.tlv = new HuaweiTLV() + .put(0x81); + + this.complete = true; + } + } + + public static class Response extends HuaweiPacket { + public AppDeviceParams params = new AppDeviceParams(); + public Response (ParamsProvider paramsProvider) { + super(paramsProvider); + } + + @Override + public void parseTlv() throws ParseException { + if(this.tlv.contains(0x81)) { + HuaweiTLV subTlv = this.tlv.getObject(0x81).getObject(0x82); + this.params.unknown1 = subTlv.getByte(0x03); + this.params.unknown2 = subTlv.getInteger(0x04); + this.params.osVersion = subTlv.getString(0x05); + this.params.screenShape = subTlv.getString(0x06); + this.params.width = subTlv.getShort(0x07); + this.params.height = subTlv.getShort(0x08); + this.params.unknown3 = subTlv.getInteger(0x09); + this.params.buildType = subTlv.getString(0x0a); + } + } + } + } + +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/FileUpload.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/FileUpload.java index 5c5793c02..600e59b8a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/FileUpload.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/FileUpload.java @@ -39,7 +39,8 @@ public class FileUpload { public static final byte watchface = 1; public static final byte music = 2; public static final byte backgroundImage = 3; - public static final byte app = 7; + public static final byte app = 6; + public static final byte firmware = 7; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/AsynchronousResponse.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/AsynchronousResponse.java index 3b99f0918..2d215747e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/AsynchronousResponse.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/AsynchronousResponse.java @@ -46,6 +46,7 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventDisplayMes import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.App; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Calls; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.CameraRemote; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig; @@ -117,6 +118,7 @@ public class AsynchronousResponse { handleFileUpload(response); handleWatchface(response); handleCameraRemote(response); + handleApp(response); } catch (Request.ResponseParseException e) { LOG.error("Response parse exception", e); } @@ -503,6 +505,22 @@ public class AsynchronousResponse { } } + private void handleApp(HuaweiPacket response) throws Request.ResponseParseException { + if (response.serviceId == App.id) { + if (response.commandId == 0x2) { + try { + byte status = response.getTlv().getByte(0x1); + if(status == (byte)0x66 || status == (byte)0x69) { + this.support.getHuaweiAppManager().requestAppList(); + } + } catch (HuaweiPacket.MissingTagException e) { + LOG.error("Could not send watchface confirm request", e); + } + + } + } + } + private void handleWeatherCheck(HuaweiPacket response) { if (response.serviceId == Weather.id && response.commandId == 0x04) { // Send back ok diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiAppManager.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiAppManager.java new file mode 100644 index 000000000..75e392942 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiAppManager.java @@ -0,0 +1,162 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.App; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetAppNames; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendAppDelete; + +public class HuaweiAppManager { + + static Logger LOG = LoggerFactory.getLogger(HuaweiAppManager.class); + + public static class AppConfig { + + public String bundleName; + public String vendor; + public String version; + + JSONObject distroFilters = null; + + public AppConfig(String jsonStr) { + + try { + JSONObject config = new JSONObject(jsonStr); + this.bundleName = config.getJSONObject("app").getString("bundleName"); + this.vendor = config.getJSONObject("app").getString("vendor"); + this.version = config.getJSONObject("app").getJSONObject("version").getString("name"); + + parseDistroFilters(config); + + } catch (Exception e) { + LOG.error("Error decode app config", e); + } + } + + private void parseDistroFilters(JSONObject config) { + try { + distroFilters = config.getJSONObject("module").getJSONObject("distroFilter"); + } catch (Exception e) { + LOG.error("Error decode app config distroFilter", e); + } + } + + private boolean isValInArray(JSONArray arr, String value) throws JSONException { + for (int i = 0; i < arr.length(); i++) { + if (arr.getString(i).equals(value)) + return true; + } + return false; + } + + public boolean checkDistroFilters(String screenShape, String screenWindow) { + if (distroFilters == null) + return false; + try { + boolean screenShapeSupported = false; + boolean screenWindowSupported = false; + if (distroFilters.has("screenShape")) { + JSONArray values = distroFilters.getJSONObject("screenShape").getJSONArray("value"); + String policy = distroFilters.getJSONObject("screenShape").getString("policy"); + screenShapeSupported = isValInArray(values, screenShape) && policy.equals("include"); + } + if (distroFilters.has("screenWindow")) { + JSONArray values = distroFilters.getJSONObject("screenWindow").getJSONArray("value"); + String policy = distroFilters.getJSONObject("screenWindow").getString("policy"); + screenWindowSupported = isValInArray(values, screenWindow) && policy.equals("include"); + } + return screenShapeSupported && screenWindowSupported; + } catch (Exception e) { + LOG.error("Error decode app config distroFilter", e); + return false; + } + } + + } + + private final HuaweiSupportProvider support; + + private List installedAppList = null; + + public HuaweiAppManager(HuaweiSupportProvider support) { + this.support = support; + } + + public void setInstalledAppList(List installedAppList) { + this.installedAppList = installedAppList; + handleAppList(); + } + + public void handleAppList() { + if (this.installedAppList == null) { + return; + } + + final List gbDeviceApps = new ArrayList<>(); + + for (final App.InstalledAppInfo appInfo : installedAppList) { + final UUID uuid = UUID.nameUUIDFromBytes(appInfo.packageName.getBytes()); + GBDeviceApp gbDeviceApp = new GBDeviceApp( + uuid, + appInfo.appName, + "", + appInfo.version, + GBDeviceApp.Type.APP_GENERIC + ); + gbDeviceApps.add(gbDeviceApp); + } + support.setGbWatchApps(gbDeviceApps); + } + + public void requestAppList() { + if (!this.support.getHuaweiCoordinator().supportsAppParams()) + return; + try { + GetAppNames getAppNames = new GetAppNames(support); + getAppNames.doPerform(); + } catch (IOException e) { + LOG.error("Error request applications list", e); + } + } + + public boolean startApp(UUID uuid) { + if (this.installedAppList == null) + return false; + + for (final App.InstalledAppInfo appInfo : installedAppList) { + final UUID appUuid = UUID.nameUUIDFromBytes(appInfo.packageName.getBytes()); + if (appUuid.equals(uuid)) + return true; + } + return false; + } + + public boolean deleteApp(UUID uuid) { + if (this.installedAppList == null) + return false; + + for (final App.InstalledAppInfo appInfo : installedAppList) { + final UUID appUuid = UUID.nameUUIDFromBytes(appInfo.packageName.getBytes()); + if (appUuid.equals(uuid)) { + try { + SendAppDelete sendAppDelete = new SendAppDelete(support, appInfo.packageName); + sendAppDelete.doPerform(); + } catch (IOException e) { + LOG.error("Could not delete app: " + appInfo.packageName, e); + } + return true; + } + } + return false; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiFwHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiFwHelper.java index 1f39dca4d..71867e453 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiFwHelper.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiFwHelper.java @@ -25,10 +25,13 @@ import android.net.Uri; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStream; import java.nio.ByteBuffer; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiAppParser; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileUpload; import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GBZipFile; @@ -47,6 +50,7 @@ public class HuaweiFwHelper { Bitmap watchfacePreviewBitmap; HuaweiWatchfaceManager.WatchfaceDescription watchfaceDescription; + HuaweiAppManager.AppConfig appConfig; Context mContext; public HuaweiFwHelper(final Uri uri, final Context context) { @@ -65,13 +69,67 @@ public class HuaweiFwHelper { } private void parseFile() { - if (parseAsWatchFace()) { + if(parseAsApp()) { + assert appConfig.bundleName != null; + assert appConfig.distroFilters != null; + fileType = FileUpload.Filetype.app; + } else if (parseAsWatchFace()) { assert watchfaceDescription.screen != null; assert watchfaceDescription.title != null; fileType = FileUpload.Filetype.watchface; } } + boolean parseAsApp() { + + try { + final UriHelper uriHelper = UriHelper.get(uri, this.mContext); + + InputStream inputStream = uriHelper.openInputStream(); + + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + + int nRead; + byte[] data = new byte[4]; + + while ((nRead = inputStream.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, nRead); + } + + buffer.flush(); + byte[] hap_data = buffer.toByteArray(); + + HuaweiAppParser app = new HuaweiAppParser(); + app.parseData(hap_data); + + byte[] config = app.getEntryContent("config.json"); + if(config == null) + return false; + appConfig = new HuaweiAppManager.AppConfig(new String(config)); + fileName = app.getPackageName() + "_INSTALL"; //TODO: INSTALL or UPDATE suffix + + fw = hap_data; + fileSize = fw.length; + + byte[] icon = app.getEntryContent("icon_small.png"); + if(icon != null) { + watchfacePreviewBitmap = BitmapFactory.decodeByteArray(icon, 0, icon.length); + } + + return true; + + } catch (FileNotFoundException e) { + LOG.error("The watchface file was not found.", e); + } catch (IOException e) { + LOG.error("General IO error occurred.", e); + } catch (Exception e) { + LOG.error("Unknown error occurred.", e); + } + + return false; + } + + public byte[] getBytes() { return fw; } @@ -137,8 +195,13 @@ public class HuaweiFwHelper { public boolean isWatchface() { return fileType == FileUpload.Filetype.watchface; } + + public boolean isAPP() { + return fileType == FileUpload.Filetype.app; + } + public boolean isValid() { - return isWatchface(); + return isWatchface() || isAPP(); } public Bitmap getWatchfacePreviewBitmap() { @@ -149,6 +212,10 @@ public class HuaweiFwHelper { return watchfaceDescription; } + public HuaweiAppManager.AppConfig getAppconfig() { + return appConfig; + } + public byte getFileType() { return fileType; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java index e7c27698b..a2de11da9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java @@ -46,6 +46,7 @@ import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSett import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCameraRemote; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventDisplayMessage; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; @@ -78,6 +79,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.Alarm; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; import nodomain.freeyourgadget.gadgetbridge.entities.Device; import nodomain.freeyourgadget.gadgetbridge.entities.User; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; @@ -89,6 +91,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.AcceptAgreementsRequest; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetAppInfoParams; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetEventAlarmList; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetGpsParameterRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetNotificationConstraintsRequest; @@ -204,6 +207,8 @@ public class HuaweiSupportProvider { protected HuaweiFileDownloadManager huaweiFileDownloadManager = new HuaweiFileDownloadManager(this); + protected HuaweiAppManager huaweiAppManager = new HuaweiAppManager(this); + public HuaweiCoordinatorSupplier getCoordinator() { return ((HuaweiCoordinatorSupplier) this.gbDevice.getDeviceCoordinator()); } @@ -215,6 +220,9 @@ public class HuaweiSupportProvider { return huaweiWatchfaceManager; } + public HuaweiAppManager getHuaweiAppManager() { + return huaweiAppManager; + } public HuaweiSupportProvider(HuaweiBRSupport support) { this.brSupport = support; } @@ -802,6 +810,11 @@ public class HuaweiSupportProvider { SendCameraRemoteSetupEvent sendCameraRemoteSetupEvent = new SendCameraRemoteSetupEvent(this, CameraRemote.CameraRemoteSetup.Request.Event.ENABLE_CAMERA); sendCameraRemoteSetupEvent.doPerform(); } + + if (getHuaweiCoordinator().supportsAppParams()) { + GetAppInfoParams getAppInfoParams = new GetAppInfoParams(this); + getAppInfoParams.doPerform(); + } } catch (IOException e) { GB.toast(getContext(), "Initialize dynamic services of Huawei device failed", Toast.LENGTH_SHORT, GB.ERROR, e); @@ -2029,19 +2042,50 @@ public class HuaweiSupportProvider { LOG.error("Failed to update progress notification", e); } } + private List gbWatchFaces = null; + private List gbWatchApps = null; + + public void setGbWatchFaces(List gbWatchFaces) { + this.gbWatchFaces = gbWatchFaces; + updateAppList(); + } + + public void setGbWatchApps(List gbWatchApps) { + this.gbWatchApps = gbWatchApps; + updateAppList(); + } + private void updateAppList() { + ArrayList gbDeviceApps=new ArrayList<>(); + if(this.gbWatchFaces != null) + gbDeviceApps.addAll(this.gbWatchFaces); + if(this.gbWatchApps != null) + gbDeviceApps.addAll(this.gbWatchApps); + final GBDeviceEventAppInfo appInfoCmd = new GBDeviceEventAppInfo(); + appInfoCmd.apps = gbDeviceApps.toArray(new GBDeviceApp[0]); + evaluateGBDeviceEvent(appInfoCmd); + } public void onAppInfoReq() { + this.gbWatchFaces = null; + this.gbWatchApps = null; huaweiWatchfaceManager.requestWatchfaceList(); + huaweiAppManager.requestAppList(); } public void onAppStart(final UUID uuid, boolean start) { if (start) { - huaweiWatchfaceManager.setWatchface(uuid); + //NOTE: to prevent exception in watchfaces code + if(!huaweiAppManager.startApp(uuid)) { + huaweiWatchfaceManager.setWatchface(uuid); + } } } public void onAppDelete(final UUID uuid) { - huaweiWatchfaceManager.deleteWatchface(uuid); + //NOTE: to prevent exception in watchfaces code + if(!huaweiAppManager.deleteApp(uuid)){ + huaweiWatchfaceManager.deleteWatchface(uuid); + } } public void onCameraStatusChange(GBDeviceEventCameraRemote.Event event, String filename) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiUploadManager.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiUploadManager.java index b73941461..9be852a0e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiUploadManager.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiUploadManager.java @@ -24,6 +24,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileUpload; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileUpload.FileUploadParams; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.util.GB; @@ -132,7 +133,11 @@ public class HuaweiUploadManager { public void setDeviceBusy() { final GBDevice device = support.getDevice(); - device.setBusyTask(support.getContext().getString(R.string.uploading_watchface)); + if(fileType == FileUpload.Filetype.watchface) { + device.setBusyTask(support.getContext().getString(R.string.uploading_watchface)); + } else { + device.setBusyTask(support.getContext().getString(R.string.updating_firmware)); + } device.sendDeviceUpdateIntent(support.getContext()); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiWatchfaceManager.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiWatchfaceManager.java index b6650136d..8e398345c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiWatchfaceManager.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiWatchfaceManager.java @@ -26,20 +26,16 @@ 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; @@ -195,10 +191,7 @@ public class HuaweiWatchfaceManager ); gbDeviceApps.add(gbDeviceApp); } - - final GBDeviceEventAppInfo appInfoCmd = new GBDeviceEventAppInfo(); - appInfoCmd.apps = gbDeviceApps.toArray(new GBDeviceApp[0]); - support.evaluateGBDeviceEvent(appInfoCmd); + support.setGbWatchFaces(gbDeviceApps); } public void updateWatchfaceNames() { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetAppInfoParams.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetAppInfoParams.java new file mode 100644 index 000000000..88bd56b21 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetAppInfoParams.java @@ -0,0 +1,35 @@ +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.App; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider; + +public class GetAppInfoParams extends Request{ + + public GetAppInfoParams(HuaweiSupportProvider support) { + super(support); + this.serviceId = App.id; + this.commandId = App.AppInfoParams.id; + + } + + @Override + protected List createRequest() throws RequestCreationException { + try { + return new App.AppInfoParams.Request(paramsProvider).serialize(); + } catch (HuaweiPacket.CryptoException e) { + throw new RequestCreationException(e); + } + } + + @Override + protected void processResponse() throws ResponseParseException { + if (!(receivedPacket instanceof App.AppInfoParams.Response)) + throw new ResponseTypeMismatchException(receivedPacket, App.AppInfoParams.Response.class); + + App.AppInfoParams.Response resp = (App.AppInfoParams.Response)(receivedPacket); + supportProvider.getHuaweiCoordinator().setAppDeviceParams(resp.params); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetAppNames.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetAppNames.java new file mode 100644 index 000000000..5a7cd5479 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetAppNames.java @@ -0,0 +1,33 @@ +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.App; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider; + +public class GetAppNames extends Request{ + public GetAppNames(HuaweiSupportProvider support) { + super(support); + this.serviceId = App.id; + this.commandId = App.AppNames.id; + } + + @Override + protected List createRequest() throws RequestCreationException { + try { + return new App.AppNames.Request(paramsProvider).serialize(); + } catch (HuaweiPacket.CryptoException e) { + throw new RequestCreationException(e); + } + } + + @Override + protected void processResponse() throws ResponseParseException { + if (!(receivedPacket instanceof App.AppNames.Response)) + throw new ResponseTypeMismatchException(receivedPacket, App.AppNames.Response.class); + + App.AppNames.Response resp = (App.AppNames.Response)(receivedPacket); + supportProvider.getHuaweiAppManager().setInstalledAppList(resp.appInfoList); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendAppDelete.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendAppDelete.java new file mode 100644 index 000000000..01bb819e1 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendAppDelete.java @@ -0,0 +1,28 @@ +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.App; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider; + +public class SendAppDelete extends Request { + private final String packageName; + public SendAppDelete(HuaweiSupportProvider support, String packageName) { + super(support); + this.serviceId = App.id; + this.commandId = App.AppDelete.id; + this.packageName = packageName; + } + + @Override + protected List createRequest() throws RequestCreationException { + try { + return new App.AppDelete.Request(this.paramsProvider, this.packageName).serialize(); + } catch (HuaweiPacket.CryptoException e) { + throw new RequestCreationException(e); + } + } + +} +