1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-09-10 00:06:44 +02:00

Merge remote-tracking branch 'freeyourgadget/master'

This commit is contained in:
Gordon Williams 2022-06-15 13:40:41 +01:00
commit c7351156ca
7 changed files with 134 additions and 80 deletions

View File

@ -160,10 +160,8 @@ android {
resValue "string", "about_activity_title", "@string/about_activity_title_banglejs_main"
resValue "string", "about_description", "@string/about_description_banglejs_main"
resValue "string", "gadgetbridge_running", "@string/gadgetbridge_running_banglejs_main"
targetSdkVersion 30 // SDK 30 needed for play store
// SDK 30 stops us querying package names - https://developer.android.com/training/package-visibility
// which we need for getting app name from notifications. We have to request android.permission.QUERY_ALL_PACKAGES
// as well for SDK 30
targetSdkVersion 30 // Bangle.js flavor only - We need SDK 30 for play store
// Note: app/src/banglejs/AndroidManifest.xml contains some extra permissions we need to make SDK 30 work
}
}

View File

@ -2,4 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>
<!-- SDK 30 https://developer.android.com/training/package-visibility for getting app name from notifications -->
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
</manifest>

View File

@ -351,79 +351,7 @@ public class HybridHRWatchfaceDesignerActivity extends AbstractGBActivity implem
for (int i = 0; i < layout.length(); i++) {
JSONObject layoutItem = layout.getJSONObject(i);
if (layoutItem.getString("type").equals("comp")) {
String widgetName = layoutItem.getString("name");
String widgetTimezone = null;
int widgetUpdateTimeout = -1;
boolean widgetTimeoutHideText = true;
boolean widgetTimeoutShowCircle = true;
switch (widgetName) {
case "dateSSE":
widgetName = "widgetDate";
break;
case "weatherSSE":
widgetName = "widgetWeather";
break;
case "stepsSSE":
widgetName = "widgetSteps";
break;
case "hrSSE":
widgetName = "widgetHR";
break;
case "batterySSE":
widgetName = "widgetBattery";
break;
case "caloriesSSE":
widgetName = "widgetCalories";
break;
case "activeMinutesSSE":
widgetName = "widgetActiveMins";
break;
case "chanceOfRainSSE":
widgetName = "widgetChanceOfRain";
break;
case "timeZone2SSE":
widgetName = "widget2ndTZ";
break;
}
int widgetColor = layoutItem.getString("color").equals("white") ? HybridHRWatchfaceWidget.COLOR_WHITE : HybridHRWatchfaceWidget.COLOR_BLACK;
if (widgetName.startsWith("widget2ndTZ")) {
try {
widgetName = "widget2ndTZ";
JSONObject widgetData = layoutItem.getJSONObject("data");
widgetTimezone = widgetData.getString("tzName");
widgets.add(new HybridHRWatchfaceWidget(widgetName,
layoutItem.getJSONObject("pos").getInt("x"),
layoutItem.getJSONObject("pos").getInt("y"),
layoutItem.getJSONObject("size").getInt("w"),
layoutItem.getJSONObject("size").getInt("h"),
widgetColor,
widgetTimezone));
} catch (JSONException e) {
LOG.error("Couldn't determine tzName!", e);
}
} else if (widgetName.startsWith("widgetCustom")) {
widgetName = "widgetCustom";
JSONObject widgetData = layoutItem.getJSONObject("data");
widgetUpdateTimeout = widgetData.getInt("update_timeout");
widgetTimeoutHideText = widgetData.getBoolean("timeout_hide_text");
widgetTimeoutShowCircle = widgetData.getBoolean("timeout_show_circle");
widgets.add(new HybridHRWatchfaceWidget(widgetName,
layoutItem.getJSONObject("pos").getInt("x"),
layoutItem.getJSONObject("pos").getInt("y"),
layoutItem.getJSONObject("size").getInt("w"),
layoutItem.getJSONObject("size").getInt("h"),
widgetColor,
widgetUpdateTimeout,
widgetTimeoutHideText,
widgetTimeoutShowCircle));
} else {
widgets.add(new HybridHRWatchfaceWidget(widgetName,
layoutItem.getJSONObject("pos").getInt("x"),
layoutItem.getJSONObject("pos").getInt("y"),
layoutItem.getJSONObject("size").getInt("w"),
layoutItem.getJSONObject("size").getInt("h"),
widgetColor));
}
widgets.add(HybridHRWatchfaceFactory.parseWidgetJSON(layoutItem));
}
}
} catch (JSONException e) {
@ -838,6 +766,11 @@ public class HybridHRWatchfaceDesignerActivity extends AbstractGBActivity implem
GBApplication.deviceService().onInstallApp(tempAppFileUri);
FossilHRInstallHandler.saveAppInCache(fossilFile, processedBackgroundImage, mCoordinator, HybridHRWatchfaceDesignerActivity.this);
}
Bitmap previewImage = wfFactory.getPreviewImage(this);
File previewFile = new File(cacheDir, app.getUUID().toString() + "_preview.png");
FileOutputStream previewFOS = new FileOutputStream(previewFile);
previewImage.compress(Bitmap.CompressFormat.PNG, 9, previewFOS);
previewFOS.close();
}
} catch (IOException e) {
LOG.warn("Error while creating and uploading watchface", e);

View File

@ -18,6 +18,7 @@ package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import org.json.JSONArray;
import org.json.JSONException;
@ -33,12 +34,16 @@ import java.util.LinkedHashMap;
import java.util.TimeZone;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image.ImageConverter;
import nodomain.freeyourgadget.gadgetbridge.util.BitmapUtil;
public class HybridHRWatchfaceFactory {
private final Logger LOG = LoggerFactory.getLogger(HybridHRWatchfaceFactory.class);
private static final Logger LOG = LoggerFactory.getLogger(HybridHRWatchfaceFactory.class);
private String watchfaceName;
private HybridHRWatchfaceSettings settings;
private Bitmap background;
private Bitmap previewImage;
private static final int PREVIEW_WIDTH = 192;
private static final int PREVIEW_HEIGHT = 192;
private ArrayList<JSONObject> widgets = new ArrayList<>();
public HybridHRWatchfaceFactory(String name) {
@ -140,6 +145,8 @@ public class HybridHRWatchfaceFactory {
public byte[] getWapp(Context context) throws IOException {
byte[] backgroundBytes = ImageConverter.encodeToRawImage(ImageConverter.get2BitsRAWImageBytes(background));
InputStream backgroundStream = new ByteArrayInputStream(backgroundBytes);
byte[] previewBytes = ImageConverter.encodeToRLEImage(ImageConverter.get2BitsRLEImageBytes(Bitmap.createScaledBitmap(getPreviewImage(context), PREVIEW_WIDTH, PREVIEW_HEIGHT, true)), PREVIEW_HEIGHT, PREVIEW_WIDTH);
InputStream previewStream = new ByteArrayInputStream(previewBytes);
LinkedHashMap<String, InputStream> code = new LinkedHashMap<>();
try {
code.put(watchfaceName, context.getAssets().open("fossil_hr/openSourceWatchface.bin"));
@ -163,6 +170,7 @@ public class HybridHRWatchfaceFactory {
LinkedHashMap<String, InputStream> icons = new LinkedHashMap<>();
try {
icons.put("background.raw", backgroundStream);
icons.put("!preview.rle", previewStream);
icons.put("icTrophy", context.getAssets().open("fossil_hr/icTrophy.rle"));
if (includeWidget("widgetWeather") > 0) icons.put("icWthClearDay", context.getAssets().open("fossil_hr/icWthClearDay.rle"));
if (includeWidget("widgetWeather") > 0) icons.put("icWthClearNite", context.getAssets().open("fossil_hr/icWthClearNite.rle"));
@ -254,4 +262,104 @@ public class HybridHRWatchfaceFactory {
return configuration.toString();
}
public static HybridHRWatchfaceWidget parseWidgetJSON(JSONObject widgetJSON) throws JSONException {
HybridHRWatchfaceWidget parsedWidget = null;
String widgetName = widgetJSON.getString("name");
String widgetTimezone = null;
int widgetUpdateTimeout = -1;
boolean widgetTimeoutHideText = true;
boolean widgetTimeoutShowCircle = true;
switch (widgetName) {
case "dateSSE":
widgetName = "widgetDate";
break;
case "weatherSSE":
widgetName = "widgetWeather";
break;
case "stepsSSE":
widgetName = "widgetSteps";
break;
case "hrSSE":
widgetName = "widgetHR";
break;
case "batterySSE":
widgetName = "widgetBattery";
break;
case "caloriesSSE":
widgetName = "widgetCalories";
break;
case "activeMinutesSSE":
widgetName = "widgetActiveMins";
break;
case "chanceOfRainSSE":
widgetName = "widgetChanceOfRain";
break;
case "timeZone2SSE":
widgetName = "widget2ndTZ";
break;
}
int widgetColor = widgetJSON.getString("color").equals("white") ? HybridHRWatchfaceWidget.COLOR_WHITE : HybridHRWatchfaceWidget.COLOR_BLACK;
if (widgetName.startsWith("widget2ndTZ")) {
try {
widgetName = "widget2ndTZ";
JSONObject widgetData = widgetJSON.getJSONObject("data");
widgetTimezone = widgetData.getString("tzName");
parsedWidget = new HybridHRWatchfaceWidget(widgetName,
widgetJSON.getJSONObject("pos").getInt("x"),
widgetJSON.getJSONObject("pos").getInt("y"),
widgetJSON.getJSONObject("size").getInt("w"),
widgetJSON.getJSONObject("size").getInt("h"),
widgetColor,
widgetTimezone);
} catch (JSONException e) {
LOG.error("Couldn't determine tzName!", e);
}
} else if (widgetName.startsWith("widgetCustom")) {
widgetName = "widgetCustom";
JSONObject widgetData = widgetJSON.getJSONObject("data");
widgetUpdateTimeout = widgetData.getInt("update_timeout");
widgetTimeoutHideText = widgetData.getBoolean("timeout_hide_text");
widgetTimeoutShowCircle = widgetData.getBoolean("timeout_show_circle");
parsedWidget = new HybridHRWatchfaceWidget(widgetName,
widgetJSON.getJSONObject("pos").getInt("x"),
widgetJSON.getJSONObject("pos").getInt("y"),
widgetJSON.getJSONObject("size").getInt("w"),
widgetJSON.getJSONObject("size").getInt("h"),
widgetColor,
widgetUpdateTimeout,
widgetTimeoutHideText,
widgetTimeoutShowCircle);
} else {
parsedWidget = new HybridHRWatchfaceWidget(widgetName,
widgetJSON.getJSONObject("pos").getInt("x"),
widgetJSON.getJSONObject("pos").getInt("y"),
widgetJSON.getJSONObject("size").getInt("w"),
widgetJSON.getJSONObject("size").getInt("h"),
widgetColor);
}
return parsedWidget;
}
public Bitmap getPreviewImage(Context context) {
if (previewImage == null) {
previewImage = BitmapUtil.getCircularBitmap(background);
Canvas previewCanvas = new Canvas(previewImage);
int widgetSize = 50;
float scaleFactor = previewImage.getWidth() / 240;
for (int i=0; i<widgets.size(); i++) {
try {
HybridHRWatchfaceWidget parsedWidget = parseWidgetJSON(widgets.get(i));
Bitmap widgetPreview = Bitmap.createScaledBitmap(parsedWidget.getPreviewImage(context), (int)(widgetSize * scaleFactor), (int)(widgetSize * scaleFactor), true);
int offsetFromCenter = (int)((widgetSize/2) * scaleFactor);
previewCanvas.drawBitmap(widgetPreview, parsedWidget.getPosX() - offsetFromCenter, parsedWidget.getPosY() - offsetFromCenter, null);
} catch (JSONException e) {
LOG.warn("Couldn't parse widget JSON", e);
} catch (IOException e) {
LOG.warn("Couldn't parse widget JSON", e);
}
}
}
return previewImage;
}
}

View File

@ -1584,6 +1584,18 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
);
queueWrite(new JsonPutRequest(responseObject, this));
}
} else if (request.optString("custom_menu").equals("request_config")) {
// watchface requests custom menu data to be initialized
LOG.info("Got custom_menu config request, sending intent to HR Menu Companion app...");
Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setClassName("d.d.hrmenucompanion", "d.d.hrmenucompanion.MainActivity");
intent.putExtra("SEND_CONFIG", true);
try {
getContext().startActivity(intent);
} catch (Exception e) {
LOG.info("Couldn't send intent to Fossil-HR-Menu-Companion app, is it installed?");
}
} else {
LOG.warn("Unhandled request from watch: " + requestJson.toString());
}

@ -1 +1 @@
Subproject commit f07ed376e9046dbcc9c5d7821117c80b2d79ffd1
Subproject commit 6a0c2bdad14e58d4cf4359e5f573c60aa89bca53