1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-09-01 03:55:47 +02:00

Huawei: Basic support for the installation of the applications

This commit is contained in:
Me7c7 2024-08-29 17:23:42 +03:00
parent d75ae6b54d
commit e54fd95a8b
15 changed files with 725 additions and 62 deletions

View File

@ -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<AppFileEntry> 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);
}
}
}

View File

@ -22,7 +22,6 @@ import android.content.SharedPreferences;
import android.net.Uri; import android.net.Uri;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.TreeMap; 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.DeviceSpecificSettings;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; 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;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Notifications.NotificationConstraintsType; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Notifications.NotificationConstraintsType;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Watchface; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Watchface;
@ -59,6 +59,8 @@ public class HuaweiCoordinator {
private Watchface.WatchfaceDeviceParams watchfaceDeviceParams; private Watchface.WatchfaceDeviceParams watchfaceDeviceParams;
private App.AppDeviceParams appDeviceParams;
private final HuaweiCoordinatorSupplier parent; private final HuaweiCoordinatorSupplier parent;
private boolean transactionCrypted=true; private boolean transactionCrypted=true;
@ -405,6 +407,8 @@ public class HuaweiCoordinator {
public boolean supportsWatchfaceParams(){ return supportsCommandForService(0x27, 0x01);} public boolean supportsWatchfaceParams(){ return supportsCommandForService(0x27, 0x01);}
public boolean supportsAppParams(){ return supportsCommandForService(0x2a, 0x06);}
public boolean supportsWeather() { public boolean supportsWeather() {
return supportsCommandForService(0x0f, 0x01); return supportsCommandForService(0x0f, 0x01);
} }
@ -584,20 +588,26 @@ public class HuaweiCoordinator {
this.watchfaceDeviceParams = watchfaceDeviceParams; this.watchfaceDeviceParams = watchfaceDeviceParams;
} }
public void setAppDeviceParams(App.AppDeviceParams appDeviceParams) {
this.appDeviceParams = appDeviceParams;
}
public App.AppDeviceParams getAppDeviceParams() {
return appDeviceParams;
}
public Class<? extends Activity> getAppManagerActivity() { public Class<? extends Activity> getAppManagerActivity() {
return AppManagerActivity.class; return AppManagerActivity.class;
} }
public boolean getSupportsAppListFetching() { public boolean getSupportsAppListFetching() { return true; }
return true;
}
public boolean getSupportsAppsManagement(GBDevice device) { public boolean getSupportsAppsManagement(GBDevice device) {
return true; return true;
} }
public boolean getSupportsInstalledAppManagement(GBDevice device) { 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) { public boolean getSupportsCachedAppManagement(GBDevice device) {

View File

@ -29,6 +29,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem; 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.HuaweiFwHelper;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiWatchfaceManager; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiWatchfaceManager;
@ -56,57 +57,108 @@ public class HuaweiInstallHandler implements InstallHandler {
return; 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(); HuaweiWatchfaceManager.Resolution resolution = new HuaweiWatchfaceManager.Resolution();
String deviceScreen = String.format("%d*%d",huaweiCoordinatorSupplier.getHuaweiCoordinator().getHeight(), String deviceScreen = String.format("%d*%d", huaweiCoordinatorSupplier.getHuaweiCoordinator().getHeight(),
huaweiCoordinatorSupplier.getHuaweiCoordinator().getWidth()); huaweiCoordinatorSupplier.getHuaweiCoordinator().getWidth());
this.valid = resolution.isValid(description.screen, deviceScreen); 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) { if (helper.getWatchfacePreviewBitmap() != null) {
installItem.setPreview(helper.getWatchfacePreviewBitmap()); 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 @Override

View File

@ -31,6 +31,7 @@ import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Alarms; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Alarms;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.AccountRelated; 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.Calls;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.CameraRemote; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.CameraRemote;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService0A; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileDownloadService0A;
@ -616,6 +617,16 @@ public class HuaweiPacket {
this.isEncrypted = this.attemptDecrypt(); // Helps with debugging this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
return this; 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: default:
this.isEncrypted = this.attemptDecrypt(); // Helps with debugging this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
return this; return this;

View File

@ -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<App.InstalledAppInfo> 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);
}
}
}
}
}

View File

@ -39,7 +39,8 @@ public class FileUpload {
public static final byte watchface = 1; public static final byte watchface = 1;
public static final byte music = 2; public static final byte music = 2;
public static final byte backgroundImage = 3; public static final byte backgroundImage = 3;
public static final byte app = 7; public static final byte app = 6;
public static final byte firmware = 7;
} }

View File

@ -46,6 +46,7 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventDisplayMes
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket; 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.Calls;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.CameraRemote; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.CameraRemote;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
@ -117,6 +118,7 @@ public class AsynchronousResponse {
handleFileUpload(response); handleFileUpload(response);
handleWatchface(response); handleWatchface(response);
handleCameraRemote(response); handleCameraRemote(response);
handleApp(response);
} catch (Request.ResponseParseException e) { } catch (Request.ResponseParseException e) {
LOG.error("Response parse exception", 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) { private void handleWeatherCheck(HuaweiPacket response) {
if (response.serviceId == Weather.id && response.commandId == 0x04) { if (response.serviceId == Weather.id && response.commandId == 0x04) {
// Send back ok // Send back ok

View File

@ -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<App.InstalledAppInfo> installedAppList = null;
public HuaweiAppManager(HuaweiSupportProvider support) {
this.support = support;
}
public void setInstalledAppList(List<App.InstalledAppInfo> installedAppList) {
this.installedAppList = installedAppList;
handleAppList();
}
public void handleAppList() {
if (this.installedAppList == null) {
return;
}
final List<GBDeviceApp> 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;
}
}

View File

@ -25,10 +25,13 @@ import android.net.Uri;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiAppParser;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileUpload; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileUpload;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.GBZipFile; import nodomain.freeyourgadget.gadgetbridge.util.GBZipFile;
@ -47,6 +50,7 @@ public class HuaweiFwHelper {
Bitmap watchfacePreviewBitmap; Bitmap watchfacePreviewBitmap;
HuaweiWatchfaceManager.WatchfaceDescription watchfaceDescription; HuaweiWatchfaceManager.WatchfaceDescription watchfaceDescription;
HuaweiAppManager.AppConfig appConfig;
Context mContext; Context mContext;
public HuaweiFwHelper(final Uri uri, final Context context) { public HuaweiFwHelper(final Uri uri, final Context context) {
@ -65,13 +69,67 @@ public class HuaweiFwHelper {
} }
private void parseFile() { 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.screen != null;
assert watchfaceDescription.title != null; assert watchfaceDescription.title != null;
fileType = FileUpload.Filetype.watchface; 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() { public byte[] getBytes() {
return fw; return fw;
} }
@ -137,8 +195,13 @@ public class HuaweiFwHelper {
public boolean isWatchface() { public boolean isWatchface() {
return fileType == FileUpload.Filetype.watchface; return fileType == FileUpload.Filetype.watchface;
} }
public boolean isAPP() {
return fileType == FileUpload.Filetype.app;
}
public boolean isValid() { public boolean isValid() {
return isWatchface(); return isWatchface() || isAPP();
} }
public Bitmap getWatchfacePreviewBitmap() { public Bitmap getWatchfacePreviewBitmap() {
@ -149,6 +212,10 @@ public class HuaweiFwHelper {
return watchfaceDescription; return watchfaceDescription;
} }
public HuaweiAppManager.AppConfig getAppconfig() {
return appConfig;
}
public byte getFileType() { public byte getFileType() {
return fileType; return fileType;
} }

View File

@ -46,6 +46,7 @@ import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSett
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCameraRemote; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCameraRemote;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventDisplayMessage; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventDisplayMessage;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; 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.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device; import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.User; import nodomain.freeyourgadget.gadgetbridge.entities.User;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; 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.btle.actions.SetDeviceStateAction;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.AcceptAgreementsRequest; 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.GetEventAlarmList;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetGpsParameterRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetGpsParameterRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetNotificationConstraintsRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetNotificationConstraintsRequest;
@ -204,6 +207,8 @@ public class HuaweiSupportProvider {
protected HuaweiFileDownloadManager huaweiFileDownloadManager = new HuaweiFileDownloadManager(this); protected HuaweiFileDownloadManager huaweiFileDownloadManager = new HuaweiFileDownloadManager(this);
protected HuaweiAppManager huaweiAppManager = new HuaweiAppManager(this);
public HuaweiCoordinatorSupplier getCoordinator() { public HuaweiCoordinatorSupplier getCoordinator() {
return ((HuaweiCoordinatorSupplier) this.gbDevice.getDeviceCoordinator()); return ((HuaweiCoordinatorSupplier) this.gbDevice.getDeviceCoordinator());
} }
@ -215,6 +220,9 @@ public class HuaweiSupportProvider {
return huaweiWatchfaceManager; return huaweiWatchfaceManager;
} }
public HuaweiAppManager getHuaweiAppManager() {
return huaweiAppManager;
}
public HuaweiSupportProvider(HuaweiBRSupport support) { public HuaweiSupportProvider(HuaweiBRSupport support) {
this.brSupport = support; this.brSupport = support;
} }
@ -802,6 +810,11 @@ public class HuaweiSupportProvider {
SendCameraRemoteSetupEvent sendCameraRemoteSetupEvent = new SendCameraRemoteSetupEvent(this, CameraRemote.CameraRemoteSetup.Request.Event.ENABLE_CAMERA); SendCameraRemoteSetupEvent sendCameraRemoteSetupEvent = new SendCameraRemoteSetupEvent(this, CameraRemote.CameraRemoteSetup.Request.Event.ENABLE_CAMERA);
sendCameraRemoteSetupEvent.doPerform(); sendCameraRemoteSetupEvent.doPerform();
} }
if (getHuaweiCoordinator().supportsAppParams()) {
GetAppInfoParams getAppInfoParams = new GetAppInfoParams(this);
getAppInfoParams.doPerform();
}
} catch (IOException e) { } catch (IOException e) {
GB.toast(getContext(), "Initialize dynamic services of Huawei device failed", Toast.LENGTH_SHORT, GB.ERROR, GB.toast(getContext(), "Initialize dynamic services of Huawei device failed", Toast.LENGTH_SHORT, GB.ERROR,
e); e);
@ -2029,19 +2042,50 @@ public class HuaweiSupportProvider {
LOG.error("Failed to update progress notification", e); LOG.error("Failed to update progress notification", e);
} }
} }
private List<GBDeviceApp> gbWatchFaces = null;
private List<GBDeviceApp> gbWatchApps = null;
public void setGbWatchFaces(List<GBDeviceApp> gbWatchFaces) {
this.gbWatchFaces = gbWatchFaces;
updateAppList();
}
public void setGbWatchApps(List<GBDeviceApp> gbWatchApps) {
this.gbWatchApps = gbWatchApps;
updateAppList();
}
private void updateAppList() {
ArrayList<GBDeviceApp> 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() { public void onAppInfoReq() {
this.gbWatchFaces = null;
this.gbWatchApps = null;
huaweiWatchfaceManager.requestWatchfaceList(); huaweiWatchfaceManager.requestWatchfaceList();
huaweiAppManager.requestAppList();
} }
public void onAppStart(final UUID uuid, boolean start) { public void onAppStart(final UUID uuid, boolean start) {
if (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) { 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) { public void onCameraStatusChange(GBDeviceEventCameraRemote.Event event, String filename) {

View File

@ -24,6 +24,7 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import nodomain.freeyourgadget.gadgetbridge.R; 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.devices.huawei.packets.FileUpload.FileUploadParams;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
@ -132,7 +133,11 @@ public class HuaweiUploadManager {
public void setDeviceBusy() { public void setDeviceBusy() {
final GBDevice device = support.getDevice(); 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()); device.sendDeviceUpdateIntent(support.getContext());
} }

View File

@ -26,20 +26,16 @@ import java.io.IOException;
import java.io.StringReader; import java.io.StringReader;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Random; import java.util.Random;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilderFactory;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Watchface; 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.impl.GBDeviceApp;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetWatchfacesList; 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.GetWatchfacesNames;
@ -195,10 +191,7 @@ public class HuaweiWatchfaceManager
); );
gbDeviceApps.add(gbDeviceApp); gbDeviceApps.add(gbDeviceApp);
} }
support.setGbWatchFaces(gbDeviceApps);
final GBDeviceEventAppInfo appInfoCmd = new GBDeviceEventAppInfo();
appInfoCmd.apps = gbDeviceApps.toArray(new GBDeviceApp[0]);
support.evaluateGBDeviceEvent(appInfoCmd);
} }
public void updateWatchfaceNames() { public void updateWatchfaceNames() {

View File

@ -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<byte[]> 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);
}
}

View File

@ -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<byte[]> 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);
}
}

View File

@ -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<byte[]> createRequest() throws RequestCreationException {
try {
return new App.AppDelete.Request(this.paramsProvider, this.packageName).serialize();
} catch (HuaweiPacket.CryptoException e) {
throw new RequestCreationException(e);
}
}
}