From 73d67d4093cb9bd7eeb3d9c9ec6e3fe82f4daec1 Mon Sep 17 00:00:00 2001 From: Daniel Dakhno Date: Sat, 19 Aug 2023 20:48:53 +0000 Subject: [PATCH] Fossil/Skagen Hybrids: Embed menu_structure in watchface apps (#3245) This PR aims to optimize the method of synchronizing the menu_structure for the openSourceWatchface by making the menu_structure request from the watch obsolete. Instead, when a new menu_structure is sent to GB via the Intent `nodomain.freeyourgadget.gadgetbridge.Q_SET_MENU_STRUCTURE`, GB remembers that JSON. Next time a watchface is built, the JSON is embedded into that app package so the watch doesn't forget the structure. This requires a full GB rebuild to work properky. Reviewed-on: https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/3245 Co-authored-by: Daniel Dakhno Co-committed-by: Daniel Dakhno --- .../assets/fossil_hr/openSourceWatchface.bin | Bin 8734 -> 8438 bytes .../HybridHRWatchfaceDesignerActivity.java | 69 ++++++++++++++++-- .../qhybrid/HybridHRWatchfaceFactory.java | 7 ++ .../devices/qhybrid/QHybridConstants.java | 2 +- .../devices/qhybrid/QHybridSupport.java | 33 +++++++++ .../devices/qhybrid/adapter/WatchAdapter.java | 6 ++ .../fossil_hr/FossilHRWatchAdapter.java | 38 +++++++--- .../gadgetbridge/util/AndroidUtils.java | 16 ++++ .../activity_hybridhr_watchface_designer.xml | 38 +++++++++- app/src/main/res/values/strings.xml | 8 ++ external/fossil-hr-watchface | 2 +- 11 files changed, 196 insertions(+), 23 deletions(-) diff --git a/app/src/main/assets/fossil_hr/openSourceWatchface.bin b/app/src/main/assets/fossil_hr/openSourceWatchface.bin index 17987ff4ff46569cf79d4fb3dc61a8947ad1bc31..11c5bd35d3b960cca2b019de2c829fb8bd88b23d 100644 GIT binary patch delta 3659 zcmZWsYiu0V75?UV_vN)`ANF`-Z@iA}nRVC=nCuMknm_{a4k3_0c_btyF|jj*HX+zf zXenV$UmIE!Xr}@Sg;GSjEiK?+wIQftqe^I9@(ammD}J?qI&IZf{AdwUe^8ydyGkoo zTHW25bMHO(`_B2!xi8)L$+nT5x8J@`2mkCz0|Eg0zZC*<2mvVH`n?geYN$X{1q=q% z=^FxA7^p{prL90M3S5+cS{LAU1I-?QB>*D{)Kb9GH3HmTVCf0K&;h?6Sn37bb->bJ zKl_Xg0&W)Y*8?mE_``s|0Wd~^<|wK##sGf|s2hN90`)vl9|zbKK&=4Oi-5lgs9XgY zR|9M_;Ff^81-RD$?iS!<0T>d2y&|wI0iVji9tC(=0h|yp6b76Ka3%_bV!)>{;B5`~ zgH{FZPXKQxfTa}hmlTk41f&Kw%8Ht2p&><=bTKsnJu;w2^>9j$rBZra?;6m%CkD@I zde21sZ1Sw8Cnn0lm@4Y&qTXB7`wF`5Wcr$&|kpXkG`n*8|2jz~2Tmw*$2sfZC0mLAC>McK|+((?G2P7!Kcy zSbiEfjBQfNQ58E>3hT^{=#fj}z{Y5-Ye;GdcBmA;B+W^(7AgzfX0lO6kFD7uJ8{Sq zSEOk~1BYZmkV6r66X4#&ovQ5Qo6+X=o4tFbt-bV`d?piP7f|0tetK_7!=HQQ-x9p~ zn>1VvPPZGV>}kh60CpST-v+QR0F^rcb|+A~lgGY$7tp*5Fz)90!uA5Sy@0(zC3&M^_z_TO>LO4v@)eiyeKA>_Ks2v6>vp{VYsLXlqD-*@q9AF#){38InA8?NX z%?AMYOKpz64AhPR&Eo(&;f<@0Bxp+g1b3W01XLdKexqj9V5NEQBQ?`Si`C|VDVRdT z`;(ef>4`t@WmqPdPRNsUv{Plln`YVndrX^G7Q9m|n+-M#wUKS3+Cjax;Jw8rf|=wV zE$A)+{-XB@%c|sg<)oJhW#Y65QOHgKl~dmK(0DcQw(SKw!DEpo*G~a?K}{c^ara2Dnv@n_q3m`KHTM)MiP| z9_Ms6ALrp?iOP(cQ!FqoZ;7GVur_SU=O-g(^r9IX4Vzs@!m1Dzx-2oS$%0yzx-Za_ z9!;}E&4PAF3zd;DrG_OcZZgL#F;zIC^;u%t?bRY>q0iJCLWsux#x$y26h^tmi?TX8 zTKPI)e1o4Z(BREL?VEuAEx?ueDVWH0p7fk_-Iow(dM6x6(bOsPE;S*SsuCU zDPA|4PjU6>AHq=ms* zriIHw*1}-J!g?**F^rmu#-@f$p)t)_B?%B9ixP{**fT)$8K9E_lq96h9Ua~SV4J*M z5iGDvk{v2l!!7YXQPrgBD=*1qF=9p=)9ucUD1!X@c$BCAX=fB|IQ;*ugk+iI%W4x>((7TQf zQh!S)Bn_h;H)utR-bXzdVZ{4)k0IheURK+gTDHX4jIN#87#30LH#6rXZOD668#o{w zu+X=UXv*6P0k+}GQU&wgrDyniMh%)JhPn?Mmj6{MQrg~>_-6QAd8C`McN<*?jSDz{If-SE}Ca$zq zo8^VjwaS_@6}FYew8wh)Wf)G(%`Y5XSOmi<`t{&($e5j3oLPCP_qRe%9LMI4pFFs* zIDhi+;>r0r?;d?*<*fds;7w&Rk(0+~=Z+jbJ~s>Rj?5odu3z`G=>2){m3R~ji!<|! d2N#bXo14eVyV+gBN_MDM@V=8B^CohC|1YJr#HIiM delta 4156 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 diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRWatchfaceDesignerActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRWatchfaceDesignerActivity.java index ef481e5b3..e482b3371 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRWatchfaceDesignerActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HybridHRWatchfaceDesignerActivity.java @@ -23,6 +23,7 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; +import android.content.SharedPreferences; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -39,6 +40,7 @@ import android.view.DragEvent; import android.view.Menu; import android.view.MenuItem; import android.view.View; +import android.widget.Button; import android.widget.EditText; import android.widget.ImageView; import android.widget.LinearLayout; @@ -73,6 +75,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport; +import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils; import nodomain.freeyourgadget.gadgetbridge.util.BitmapUtil; import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; import nodomain.freeyourgadget.gadgetbridge.util.GB; @@ -154,6 +157,14 @@ public class HybridHRWatchfaceDesignerActivity extends AbstractGBActivity implem findViewById(R.id.watchface_rotate_left).setOnClickListener(this); findViewById(R.id.watchface_rotate_right).setOnClickListener(this); findViewById(R.id.watchface_remove_image).setOnClickListener(this); + findViewById(R.id.button_watchface_open_menu_companion).setOnClickListener(this); + findViewById(R.id.button_watchface_reset_menu_structure).setOnClickListener(this); + } + + @Override + protected void onResume() { + super.onResume(); + reloadMenuStructureIndicator(); } @Override @@ -207,7 +218,8 @@ public class HybridHRWatchfaceDesignerActivity extends AbstractGBActivity implem @Override public void onClick(View v) { - if (v.getId() == R.id.button_edit_name) { + int buttonId = v.getId(); + if (buttonId == R.id.button_edit_name) { final EditText input = new EditText(this); input.setText(watchfaceName); input.setId(0); @@ -226,7 +238,7 @@ public class HybridHRWatchfaceDesignerActivity extends AbstractGBActivity implem }) .setTitle(R.string.watchface_dialog_title_set_name) .show(); - } else if (v.getId() == R.id.watchface_invert_colors) { + } else if (buttonId == R.id.watchface_invert_colors) { if (selectedBackgroundImage != null) { selectedBackgroundImage = BitmapUtil.invertBitmapColors(selectedBackgroundImage); for (int i=0; i widgets = new ArrayList<>(); + private JSONObject menuStructure = new JSONObject(); public HybridHRWatchfaceFactory(String name) { watchfaceName = name.replaceAll("[^-A-Za-z0-9]", ""); @@ -130,6 +131,10 @@ public class HybridHRWatchfaceFactory { } } + public void setMenuStructure(JSONObject menuStructure){ + this.menuStructure = menuStructure; + } + public void addWidgets(ArrayList widgets) { for (HybridHRWatchfaceWidget widget : widgets) { addWidget(widget); @@ -311,6 +316,8 @@ public class HybridHRWatchfaceFactory { config.put("light_up_on_notification", settings.getLightUpOnNotification()); configuration.put("config", config); + configuration.put("menu_structure", menuStructure); + return configuration.toString(); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/QHybridConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/QHybridConstants.java index 24e8b1ae3..f54f40fda 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/QHybridConstants.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/QHybridConstants.java @@ -22,7 +22,7 @@ import java.util.Map; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; public final class QHybridConstants { - public static final String HYBRIDHR_WATCHFACE_VERSION = "1.10"; + public static final String HYBRIDHR_WATCHFACE_VERSION = "1.11"; public static final int HYBRID_HR_WATCHFACE_WIDGET_SIZE = 76; public static Map KNOWN_WAPP_VERSIONS = new HashMap() { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/QHybridSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/QHybridSupport.java index bfee4c841..3c757b304 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/QHybridSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/QHybridSupport.java @@ -30,6 +30,8 @@ import android.widget.Toast; import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import org.json.JSONException; +import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -99,6 +101,8 @@ public class QHybridSupport extends QHybridBaseSupport { public static final String QHYBRID_COMMAND_DOWNLOAD_FILE = "nodomain.freeyourgadget.gadgetbridge.Q_DOWNLOAD_FILE"; public static final String QHYBRID_COMMAND_UPLOAD_FILE = "nodomain.freeyourgadget.gadgetbridge.Q_UPLOAD_FILE"; + public static final String QHYBRID_COMMAND_SET_MENU_STRUCTURE = "nodomain.freeyourgadget.gadgetbridge.Q_SET_MENU_STRUCTURE"; + public static final String QHYBRID_ACTION_DOWNLOADED_FILE = "nodomain.freeyourgadget.gadgetbridge.Q_DOWNLOADED_FILE"; public static final String QHYBRID_ACTION_UPLOADED_FILE = "nodomain.freeyourgadget.gadgetbridge.Q_UPLOADED_FILE"; @@ -305,6 +309,7 @@ public class QHybridSupport extends QHybridBaseSupport { globalFilter.addAction(QHYBRID_COMMAND_UPLOAD_FILE); globalFilter.addAction(QHYBRID_COMMAND_PUSH_CONFIG); globalFilter.addAction(QHYBRID_COMMAND_SWITCH_WATCHFACE); + globalFilter.addAction(QHYBRID_COMMAND_SET_MENU_STRUCTURE); globalCommandReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -380,6 +385,10 @@ public class QHybridSupport extends QHybridBaseSupport { handleSwitchWatchfaceIntent(intent); break; } + case QHYBRID_COMMAND_SET_MENU_STRUCTURE:{ + handleSetMenuStructure(intent); + break; + } } } }; @@ -398,6 +407,30 @@ public class QHybridSupport extends QHybridBaseSupport { } } + private void handleSetMenuStructure(Intent intent){ + if(intent == null){ + logger.error("intent null"); + return; + } + String menuStructureJson = intent.getStringExtra("EXTRA_MENU_STRUCTURE_JSON"); + if(menuStructureJson == null){ + logger.error("Menu structure json null"); + return; + } + if(menuStructureJson.isEmpty()){ + logger.error("Menu structure json empty"); + return; + } + try { + JSONObject menuStructure = new JSONObject(menuStructureJson); + watchAdapter.handleSetMenuStructure(menuStructure); + GB.toast(getContext().getString(R.string.info_menu_structure_set), Toast.LENGTH_SHORT, GB.INFO); + } catch (JSONException e) { + logger.error("Menu structure json empty"); + GB.toast(getContext().getString(R.string.error_invalid_menu_structure), Toast.LENGTH_SHORT, GB.ERROR); + } + } + private boolean dangerousIntentsAllowed(){ return GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(DeviceSettingsPreferenceConst.PREF_HYBRID_HR_DANGEROUS_EXTERNAL_INTENTS, true); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/WatchAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/WatchAdapter.java index ee4aa24b4..68cef9193 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/WatchAdapter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/WatchAdapter.java @@ -21,6 +21,8 @@ import android.bluetooth.BluetoothGattCharacteristic; import android.content.Context; import android.net.Uri; +import org.json.JSONObject; + import java.util.ArrayList; import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.NotificationConfiguration; @@ -165,4 +167,8 @@ public abstract class WatchAdapter { public void pushConfigJson(String configJson){ } + + public void handleSetMenuStructure(JSONObject menuStructure) { + + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java index db2b32d35..a55f6f68d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java @@ -1068,6 +1068,28 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter { } } + @Override + public void handleSetMenuStructure(JSONObject menuStructure) { + String serialized = menuStructure.toString(); + getDeviceSpecificPreferences() + .edit() + .putString("MENU_STRUCTURE_JSON", serialized) + .apply(); + + try { + String payload = new JSONObject() + .put("push", new JSONObject() + .put("set", new JSONObject() + .put("customWatchFace._.config.menu_structure", menuStructure) + ) + ).toString(); + pushConfigJson(payload); + } catch (JSONException e) { + throw new RuntimeException(e); + } + + } + @Override public void setWidgetContent(String widgetID, String content, boolean renderOnWatch) { boolean update = false; @@ -1898,16 +1920,12 @@ 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?"); + PackageManager manager = getContext().getPackageManager(); + try{ + // only show toast when companion app is installed + manager.getApplicationInfo("d.d.hrmenucompanion", 0); + GB.toast(getContext().getString(R.string.info_fossil_rebuild_watchface_custom_menu), Toast.LENGTH_SHORT, GB.INFO); + }catch (PackageManager.NameNotFoundException e){ } } else { LOG.warn("Unhandled request from watch: " + requestJson.toString()); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/AndroidUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/AndroidUtils.java index 71079c585..af101a6e6 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/AndroidUtils.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/AndroidUtils.java @@ -317,4 +317,20 @@ public class AndroidUtils { Toast.makeText(context, R.string.activity_error_share_failed, Toast.LENGTH_LONG).show(); } } + + public static void openWebsite(String url){ + Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); + GBApplication.getContext().startActivity(i); + } + + public static void openApp(String packageName) throws ClassNotFoundException { + Context context = GBApplication.getContext(); + + Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(packageName); + if(launchIntent == null){ + throw new ClassNotFoundException("App " + packageName + " cannot be found"); + } + GBApplication.getContext().startActivity(launchIntent); + } } diff --git a/app/src/main/res/layout/activity_hybridhr_watchface_designer.xml b/app/src/main/res/layout/activity_hybridhr_watchface_designer.xml index 9d26b67e0..69d067922 100644 --- a/app/src/main/res/layout/activity_hybridhr_watchface_designer.xml +++ b/app/src/main/res/layout/activity_hybridhr_watchface_designer.xml @@ -112,21 +112,53 @@