From 0b731611b98025538e1f0b6ebd65aea8bb9998c6 Mon Sep 17 00:00:00 2001 From: MrYoranimo Date: Sun, 7 Apr 2024 19:05:56 +0200 Subject: [PATCH] Xiaomi: refactor WidgetManager for Redmi Watch 4 The Redmi Watch 4 reports both an unsupported widget type and layout style: - The firmware supports a screen layout for a single full screens widget, which is defined by layout ID 128; - A full screen widget is a single 2x2 part, which is not supported. This commit adds support for both the new layout and the new widget type. Furthermore, this commit refactors the XiaomiWidgetManager. Previously, the supported layouts were determined by the types of parts supported by the device. However, the supported layouts are reported by the device through a bitfield in the widget capabilities message of which the purpose was unknown, which is now used to determine the supported layouts. --- .../widgets/WidgetScreenDetailsActivity.java | 9 +- .../capabilities/widgets/WidgetLayout.java | 8 +- .../capabilities/widgets/WidgetType.java | 1 + .../devices/xiaomi/XiaomiWidgetManager.java | 301 +++++++++++------- .../devices/xiaomi/XiaomiWorkoutType.java | 28 ++ .../devices/xiaomi/XiaomiPreferences.java | 18 -- app/src/main/proto/xiaomi.proto | 8 +- app/src/main/res/values/strings.xml | 2 + 8 files changed, 231 insertions(+), 144 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/widgets/WidgetScreenDetailsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/widgets/WidgetScreenDetailsActivity.java index 8245be60a..14e60489e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/widgets/WidgetScreenDetailsActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/widgets/WidgetScreenDetailsActivity.java @@ -194,7 +194,8 @@ public class WidgetScreenDetailsActivity extends AbstractGBActivity { updateWidget(cardWidgetBotLeft, labelWidgetBotLeft, 2); updateWidget(cardWidgetBotRight, labelWidgetBotRight, 3); break; - case SINGLE: + case ONE_BY_TWO_SINGLE: + case TWO_BY_TWO_SINGLE: updateWidget(cardWidgetTopLeft, labelWidgetTopLeft, -1); updateWidget(cardWidgetTopRight, labelWidgetTopRight, -1); updateWidget(cardWidgetCenter, labelWidgetCenter, 0); @@ -202,9 +203,9 @@ public class WidgetScreenDetailsActivity extends AbstractGBActivity { updateWidget(cardWidgetBotRight, labelWidgetBotRight, -1); break; case TWO: - updateWidget(cardWidgetTopLeft, labelWidgetTopLeft, 0); - updateWidget(cardWidgetTopRight, labelWidgetTopRight, 1); - updateWidget(cardWidgetCenter, labelWidgetCenter, -1); + updateWidget(cardWidgetTopLeft, labelWidgetTopLeft, -1); + updateWidget(cardWidgetTopRight, labelWidgetTopRight, 0); + updateWidget(cardWidgetCenter, labelWidgetCenter, 1); updateWidget(cardWidgetBotLeft, labelWidgetBotLeft, -1); updateWidget(cardWidgetBotRight, labelWidgetBotRight, -1); break; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/widgets/WidgetLayout.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/widgets/WidgetLayout.java index 06341d38c..bc677be2c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/widgets/WidgetLayout.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/widgets/WidgetLayout.java @@ -21,11 +21,17 @@ import androidx.annotation.StringRes; import nodomain.freeyourgadget.gadgetbridge.R; public enum WidgetLayout { + // Square screen layouts, 2x2 TOP_1_BOT_2(R.string.widget_layout_top_1_bot_2, WidgetType.WIDE, WidgetType.SMALL, WidgetType.SMALL), TOP_2_BOT_1(R.string.widget_layout_top_2_bot_1, WidgetType.SMALL, WidgetType.SMALL, WidgetType.SMALL), TOP_2_BOT_2(R.string.widget_layout_top_2_bot_2, WidgetType.SMALL, WidgetType.SMALL, WidgetType.SMALL, WidgetType.SMALL), - SINGLE(R.string.widget_layout_single, WidgetType.TALL), + TWO_BY_TWO_SINGLE(R.string.widget_layout_single, WidgetType.LARGE), + + // Narrow screen layouts, 2x1 + ONE_BY_TWO_SINGLE(R.string.widget_layout_single, WidgetType.TALL), TWO(R.string.widget_layout_two, WidgetType.SMALL, WidgetType.SMALL), + + // TODO Portrait screen layouts, 2x3 ; @StringRes diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/widgets/WidgetType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/widgets/WidgetType.java index 53612a359..93823d20e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/widgets/WidgetType.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/capabilities/widgets/WidgetType.java @@ -20,5 +20,6 @@ public enum WidgetType { SMALL, // 1x1 TALL, // 1x2 WIDE, // 2x1 + LARGE, // 2x2 ; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiWidgetManager.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiWidgetManager.java index 63649618f..e99d5261b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiWidgetManager.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiWidgetManager.java @@ -16,6 +16,8 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi; +import android.text.TextUtils; + import androidx.annotation.Nullable; import com.google.protobuf.InvalidProtocolBufferException; @@ -25,6 +27,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; @@ -60,31 +63,42 @@ public class XiaomiWidgetManager implements WidgetManager { @Override public List getSupportedWidgetLayouts() { final List layouts = new ArrayList<>(); - final Set partTypes = new HashSet<>(); - final XiaomiProto.WidgetParts rawWidgetParts = getRawWidgetParts(); - - for (final XiaomiProto.WidgetPart widgetPart : rawWidgetParts.getWidgetPartList()) { - partTypes.add(fromRawWidgetType(widgetPart.getType())); + final XiaomiProto.WidgetScreens widgetScreens = getRawWidgetScreens(); + if (!widgetScreens.hasWidgetsCapabilities() || !widgetScreens.getWidgetsCapabilities().hasSupportedLayoutStyles()) { + return Collections.emptyList(); } - if (partTypes.contains(WidgetType.WIDE) && partTypes.contains(WidgetType.SMALL)) { - layouts.add(WidgetLayout.TOP_1_BOT_2); - layouts.add(WidgetLayout.TOP_2_BOT_1); - layouts.add(WidgetLayout.TOP_2_BOT_2); - } + final int supportedBitmap = getRawWidgetScreens().getWidgetsCapabilities().getSupportedLayoutStyles(); - if (partTypes.contains(WidgetType.TALL)) { - layouts.add(WidgetLayout.SINGLE); - - if (partTypes.contains(WidgetType.SMALL)) { - layouts.add(WidgetLayout.TWO); + // highest known layout style is 0x4000 (1 << 14) + for (int i = 0; i < 15; i++) { + final int layoutStyleId = 1 << i; + if ((supportedBitmap & layoutStyleId) != 0) { + layouts.add(fromRawLayout(layoutStyleId)); } } return layouts; } + private static Collection convertWorkoutTypesToPartSubtypes(final Collection workoutTypes) { + final List subtypes = new ArrayList<>(workoutTypes.size()); + + // convert workout types to subtypes + for (final XiaomiWorkoutType workoutType : workoutTypes) { + subtypes.add(new WidgetPartSubtype( + String.valueOf(workoutType.getCode()), + workoutType.getName() + )); + } + + // sort by name before returning + Collections.sort(subtypes, (it, other) -> it.getName().compareToIgnoreCase(other.getName())); + + return subtypes; + } + @Override public List getSupportedWidgetParts(final WidgetType targetWidgetType) { final List parts = new LinkedList<>(); @@ -94,41 +108,29 @@ public class XiaomiWidgetManager implements WidgetManager { final Set seenNames = new HashSet<>(); final Set duplicatedNames = new HashSet<>(); + // get supported workout types and convert to subtypes for workout widgets + final Collection subtypes = convertWorkoutTypesToPartSubtypes(XiaomiWorkoutType.getWorkoutTypesSupportedByDevice(getDevice())); + for (final XiaomiProto.WidgetPart widgetPart : rawWidgetParts.getWidgetPartList()) { - final WidgetType type = fromRawWidgetType(widgetPart.getType()); + final WidgetPart convertedPart = fromRawWidgetPart(widgetPart, subtypes); - if (type != null && type.equals(targetWidgetType)) { - final WidgetPart newPart = new WidgetPart( - String.valueOf(widgetPart.getId()), - widgetPart.getTitle(), - type - ); - - if (widgetPart.getFunction() == 16) { - if (StringUtils.isBlank(newPart.getName())) { - newPart.setName(GBApplication.getContext().getString(R.string.menuitem_workout)); - } - - final List workoutTypes = XiaomiPreferences.getWorkoutTypes(getDevice()); - for (final XiaomiWorkoutType workoutType : workoutTypes) { - newPart.getSupportedSubtypes().add( - new WidgetPartSubtype( - String.valueOf(workoutType.getCode()), - workoutType.getName() - ) - ); - Collections.sort(newPart.getSupportedSubtypes(), (p1, p2) -> p1.getName().compareToIgnoreCase(p2.getName())); - } - } - - if (seenNames.contains(newPart.getFullName())) { - duplicatedNames.add(newPart.getFullName()); - } else { - seenNames.add(newPart.getFullName()); - } - - parts.add(newPart); + if (convertedPart == null) { + continue; } + + if (!convertedPart.getType().equals(targetWidgetType)) { + continue; + } + + final String convertedPartName = convertedPart.getName(); + if (seenNames.contains(convertedPartName)) { + duplicatedNames.add(convertedPartName); + } else { + seenNames.add(convertedPartName); + } + + parts.add(convertedPart); + seenNames.add(convertedPart.getName()); } // Ensure that all names are unique @@ -138,66 +140,94 @@ public class XiaomiWidgetManager implements WidgetManager { } } + Collections.sort(parts, (it, other) -> it.getName().compareToIgnoreCase(other.getName())); return parts; } + private WidgetPart fromRawWidgetPart(final XiaomiProto.WidgetPart widgetPart, final Collection subtypes) { + final WidgetType type = fromRawWidgetType(widgetPart.getType()); + + if (type == null) { + LOG.warn("Unknown widget type {}", widgetPart.getType()); + return null; + } + + final String stringifiedId = String.valueOf(widgetPart.getId()); + final WidgetPart convertedPart = new WidgetPart( + stringifiedId, + GBApplication.getContext().getString(R.string.widget_name_untitled, stringifiedId), + type + ); + + if (!TextUtils.isEmpty(widgetPart.getTitle())) { + convertedPart.setName(widgetPart.getTitle()); + } else { + // some models do not provide the name of the widget in the screens list, resolve it here + final XiaomiProto.WidgetPart resolvedPart = findRawPart(widgetPart.getType(), widgetPart.getId()); + if (resolvedPart != null) { + convertedPart.setName(resolvedPart.getTitle()); + } + } + + if (widgetPart.getFunction() == 16) { + if (StringUtils.isBlank(convertedPart.getName())) { + convertedPart.setName(GBApplication.getContext().getString(R.string.menuitem_workout)); + } + + if (subtypes != null) { + convertedPart.getSupportedSubtypes().addAll(subtypes); + + if (widgetPart.getSubType() != 0) { + final String widgetSubtype = String.valueOf(widgetPart.getSubType()); + + for (final WidgetPartSubtype availableSubtype : subtypes) { + if (availableSubtype.getId().equals(widgetSubtype)) { + convertedPart.setSubtype(availableSubtype); + break; + } + } + } + } + } + + if ((widgetPart.getId() & 256) != 0) { + convertedPart.setName(GBApplication.getContext().getString(R.string.widget_name_colored_tile, convertedPart.getName())); + } + + return convertedPart; + } + @Override public List getWidgetScreens() { final XiaomiProto.WidgetScreens rawWidgetScreens = getRawWidgetScreens(); - final List ret = new ArrayList<>(rawWidgetScreens.getWidgetScreenCount()); + final List convertedScreens = new ArrayList<>(rawWidgetScreens.getWidgetScreenCount()); + final Collection workoutTypes = convertWorkoutTypesToPartSubtypes(XiaomiWorkoutType.getWorkoutTypesSupportedByDevice(getDevice())); - final List workoutTypes = XiaomiPreferences.getWorkoutTypes(getDevice()); + for (final XiaomiProto.WidgetScreen rawScreen : rawWidgetScreens.getWidgetScreenList()) { + final WidgetLayout layout = fromRawLayout(rawScreen.getLayout()); - for (final XiaomiProto.WidgetScreen widgetScreen : rawWidgetScreens.getWidgetScreenList()) { - final WidgetLayout layout = fromRawLayout(widgetScreen.getLayout()); + final List convertedParts = new ArrayList<>(rawScreen.getWidgetPartCount()); - final List parts = new ArrayList<>(widgetScreen.getWidgetPartCount()); + for (final XiaomiProto.WidgetPart rawPart : rawScreen.getWidgetPartList()) { + final WidgetPart convertedPart = fromRawWidgetPart(rawPart, workoutTypes); - for (final XiaomiProto.WidgetPart widgetPart : widgetScreen.getWidgetPartList()) { - final WidgetType type = fromRawWidgetType(widgetPart.getType()); - - final WidgetPart newPart = new WidgetPart( - String.valueOf(widgetPart.getId()), - "Unknown (" + widgetPart.getId() + ")", - type - ); - - // Find the name - final XiaomiProto.WidgetPart rawPart1 = findRawPart(widgetPart.getType(), widgetPart.getId()); - if (rawPart1 != null) { - newPart.setName(rawPart1.getTitle()); + if (convertedPart == null) { + LOG.warn("Widget cannot be converted, result was null for following raw widget: {}", rawPart); + continue; } - if (widgetPart.getFunction() == 16) { - if (StringUtils.isBlank(newPart.getName())) { - newPart.setName(GBApplication.getContext().getString(R.string.menuitem_workout)); - } - } - - // Get the proper subtype, if any - if (widgetPart.getSubType() != 0) { - for (final XiaomiWorkoutType workoutType : workoutTypes) { - if (workoutType.getCode() == widgetPart.getSubType()) { - newPart.setSubtype(new WidgetPartSubtype( - String.valueOf(workoutType.getCode()), - workoutType.getName() - )); - } - } - } - - parts.add(newPart); + convertedParts.add(convertedPart); } - ret.add(new WidgetScreen( - String.valueOf(widgetScreen.getId()), + convertedScreens.add(new WidgetScreen( + String.valueOf(rawScreen.getId()), layout, - parts + convertedParts )); } - return ret; + return convertedScreens; } @Override @@ -219,26 +249,9 @@ public class XiaomiWidgetManager implements WidgetManager { public void saveScreen(final WidgetScreen widgetScreen) { final XiaomiProto.WidgetScreens rawWidgetScreens = getRawWidgetScreens(); - final int layoutNum; - switch (widgetScreen.getLayout()) { - case TOP_2_BOT_2: - layoutNum = 1; - break; - case TOP_1_BOT_2: - layoutNum = 2; - break; - case TOP_2_BOT_1: - layoutNum = 4; - break; - case TWO: - layoutNum = 256; - break; - case SINGLE: - layoutNum = 512; - break; - default: - LOG.warn("Unknown widget screens layout {}", widgetScreen.getLayout()); - return; + final int layoutNum = toRawLayout(widgetScreen.getLayout()); + if (layoutNum == -1) { + return; } XiaomiProto.WidgetScreen.Builder rawScreen = null; @@ -265,6 +278,8 @@ public class XiaomiWidgetManager implements WidgetManager { rawScreen.setLayout(layoutNum); rawScreen.clearWidgetPart(); + final Collection workoutTypes = XiaomiWorkoutType.getWorkoutTypesSupportedByDevice(getDevice()); + for (final WidgetPart newPart : widgetScreen.getParts()) { // Find the existing raw part final XiaomiProto.WidgetPart knownRawPart = findRawPart( @@ -274,14 +289,21 @@ public class XiaomiWidgetManager implements WidgetManager { final XiaomiProto.WidgetPart.Builder newRawPartBuilder = XiaomiProto.WidgetPart.newBuilder(knownRawPart); + // TODO only support subtypes on widget with type 16 if (newPart.getSubtype() != null) { - // Get the workout type as subtype - final List workoutTypes = XiaomiPreferences.getWorkoutTypes(getDevice()); - for (final XiaomiWorkoutType workoutType : workoutTypes) { - if (newPart.getSubtype().getId().equals(String.valueOf(workoutType.getCode()))) { - newRawPartBuilder.setSubType(workoutType.getCode()); - break; + try { + final int rawSubtype = Integer.parseInt(newPart.getSubtype().getId()); + + // Get the workout type as subtype + for (final XiaomiWorkoutType workoutType : workoutTypes) { + if (rawSubtype == workoutType.getCode()) { + newRawPartBuilder.setSubType(workoutType.getCode()); + break; + } } + } catch (final NumberFormatException ex) { + LOG.error("Failed to convert workout type {} to a number, defaulting to 1", newPart.getSubtype()); + newRawPartBuilder.setSubType(1); } } @@ -355,6 +377,8 @@ public class XiaomiWidgetManager implements WidgetManager { return WidgetType.WIDE; case 3: return WidgetType.TALL; + case 4: + return WidgetType.LARGE; default: LOG.warn("Unknown widget type {}", rawType); return null; @@ -369,6 +393,8 @@ public class XiaomiWidgetManager implements WidgetManager { return 2; case TALL: return 3; + case LARGE: + return 4; default: throw new IllegalArgumentException("Unknown widget type " + widgetType); } @@ -377,22 +403,57 @@ public class XiaomiWidgetManager implements WidgetManager { @Nullable private WidgetLayout fromRawLayout(final int rawLayout) { switch (rawLayout) { - case 1: + case 1: // 2x2, top 2x small, bottom 2x small return WidgetLayout.TOP_2_BOT_2; - case 2: + case 2: // 2x2, top wide, bottom 2x small return WidgetLayout.TOP_1_BOT_2; - case 4: + case 4: // 2x2, top 2x small, bottom wide return WidgetLayout.TOP_2_BOT_1; - case 256: + case 128: // 2x2, full screen + return WidgetLayout.TWO_BY_TWO_SINGLE; + case 256: // 1x2, top small, bottom small return WidgetLayout.TWO; - case 512: - return WidgetLayout.SINGLE; + case 512: // 1x2, full screen + return WidgetLayout.ONE_BY_TWO_SINGLE; + case 8: // 2x2, left tall, right 2x square + case 16: // 2x2, left 2x square, right tall + case 32: // 2x2, top wide, bottom wide + case 64: // 2x2, left tall, right tall + case 1024: // 2x3, top 2x square, bottom 2x2 square + case 2048: // 2x3, top 2x2 square, bottom 2x square + case 4096: // 2x3, top wide, bottom 2x2 square + case 8192: // 2x3, top 2x2 square, bottom wide + case 16384: // 2x3, full screen default: LOG.warn("Unknown widget screens layout {}", rawLayout); return null; } } + private int toRawLayout(final WidgetLayout layout) { + if (layout == null) { + return -1; + } + + switch (layout) { + case TOP_2_BOT_2: + return 1; + case TOP_1_BOT_2: + return 2; + case TOP_2_BOT_1: + return 4; + case TWO_BY_TWO_SINGLE: + return 128; + case TWO: + return 256; + case ONE_BY_TWO_SINGLE: + return 512; + default: + LOG.warn("Widget layout {} cannot be converted to raw variant", layout); + return -1; + } + } + @Nullable private XiaomiProto.WidgetPart findRawPart(final int type, final int id) { final XiaomiProto.WidgetParts rawWidgetParts = getRawWidgetParts(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiWorkoutType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiWorkoutType.java index 35fb453c0..0141710f4 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiWorkoutType.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/xiaomi/XiaomiWorkoutType.java @@ -18,7 +18,16 @@ package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi; import androidx.annotation.StringRes; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiPreferences; +import nodomain.freeyourgadget.gadgetbridge.util.Prefs; public class XiaomiWorkoutType { private final int code; @@ -58,4 +67,23 @@ public class XiaomiWorkoutType { return -1; } + + public static Collection getWorkoutTypesSupportedByDevice(final GBDevice device) { + final Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(device.getAddress())); + final List codes = prefs.getList(XiaomiPreferences.PREF_WORKOUT_TYPES, Collections.emptyList()); + final List ret = new ArrayList<>(codes.size()); + + for (final String code : codes) { + final int codeInt = Integer.parseInt(code); + final int codeNameStringRes = XiaomiWorkoutType.mapWorkoutName(codeInt); + ret.add(new XiaomiWorkoutType( + codeInt, + codeNameStringRes != -1 ? + GBApplication.getContext().getString(codeNameStringRes) : + GBApplication.getContext().getString(R.string.widget_unknown_workout, code) + )); + } + + return ret; + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiPreferences.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiPreferences.java index d325b972a..556c9f451 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiPreferences.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiPreferences.java @@ -91,22 +91,4 @@ public final class XiaomiPreferences { final Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress())); return prefs.getBoolean("keep_activity_data_on_device", false); } - - // FIXME this function should not be here - public static List getWorkoutTypes(final GBDevice gbDevice) { - final Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress())); - final List codes = prefs.getList(PREF_WORKOUT_TYPES, Collections.emptyList()); - final List ret = new ArrayList<>(codes.size()); - for (final String code : codes) { - final int codeInt = Integer.parseInt(code); - final int codeNameStringRes = XiaomiWorkoutType.mapWorkoutName(codeInt); - ret.add(new XiaomiWorkoutType( - codeInt, - codeNameStringRes != -1 ? - GBApplication.getContext().getString(codeNameStringRes) : - GBApplication.getContext().getString(R.string.widget_unknown_workout, code) - )); - } - return ret; - } } diff --git a/app/src/main/proto/xiaomi.proto b/app/src/main/proto/xiaomi.proto index fdc84a849..24c759f51 100644 --- a/app/src/main/proto/xiaomi.proto +++ b/app/src/main/proto/xiaomi.proto @@ -238,7 +238,13 @@ message WidgetScreens { message WidgetsCapabilities { optional uint32 minWidgets = 1; // 1 optional uint32 maxWidgets = 2; // 7 - optional uint32 unknown3 = 3; // 768 + + // bitmap: + // - 0b0000_0011_0000_0000 (768) on bands + // - 0b0000_0000_0000_0111 (7) on some square/round devices (Watch S1 Active) + // - 0b0000_0000_1000_0111 (135) on some square/round devices (Redmi Watch 4) + // - 0b0111_1100_0000_0000 (31744) on portrait devices (Band 8 Pro) + optional uint32 supportedLayoutStyles = 3; } message WidgetScreen { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fa5a87498..f85965d3f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2707,6 +2707,8 @@ Move down Please select all widgets Unknown workout - %s + %1$s (colored tile) + Untitled widget (%1$s) Navigation instructions Configure on-watch navigation app behavior Come to foreground