Merge pull request 'add BundleUtils.addToBundle' (#6284)

Reviewed-on: https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/6284
This commit is contained in:
José Rebelo
2026-06-14 19:55:46 +02:00
4 changed files with 443 additions and 30 deletions
@@ -58,6 +58,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes; import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
import nodomain.freeyourgadget.gadgetbridge.util.BundleUtils;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs; import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import nodomain.freeyourgadget.gadgetbridge.util.backup.PeriodicZipExporter; import nodomain.freeyourgadget.gadgetbridge.util.backup.PeriodicZipExporter;
@@ -328,27 +329,7 @@ public class IntentApiReceiver extends BroadcastReceiver {
} }
String option = key.substring(prefix.length()); String option = key.substring(prefix.length());
Object extra = extras.get(key); Object extra = extras.get(key);
if (extra == null) { if (!BundleUtils.addToBundle(options, key, extra)) {
options.putSerializable(option, null);
} else if (extra instanceof String value) {
options.putString(option, value);
} else if (extra instanceof Boolean value) {
options.putBoolean(option, value);
} else if (extra instanceof Integer value) {
options.putInt(option, value);
} else if (extra instanceof Long value) {
options.putLong(option, value);
} else if (extra instanceof Float value) {
options.putFloat(option, value);
} else if (extra instanceof float[] value) {
options.putFloatArray(option, value);
} else if (extra instanceof int[] value) {
options.putIntArray(option, value);
} else if (extra instanceof long[] value) {
options.putLongArray(option, value);
} else if (extra instanceof Parcelable value) {
options.putParcelable(option, value);
} else {
LOG.warn("unhandled extra {} {} {}", option, extra, extra.getClass()); LOG.warn("unhandled extra {} {} {}", option, extra, extra.getClass());
} }
} }
@@ -141,6 +141,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLESingleDevic
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions; import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEQueue; import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEQueue;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.util.BundleUtils;
import nodomain.freeyourgadget.gadgetbridge.util.EmojiConverter; import nodomain.freeyourgadget.gadgetbridge.util.EmojiConverter;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
@@ -1053,22 +1054,16 @@ public class BangleJSDeviceSupport extends AbstractBTLESingleDeviceSupport {
if (json.has("extra")) { if (json.has("extra")) {
JSONObject extra = json.getJSONObject("extra"); JSONObject extra = json.getJSONObject("extra");
Iterator<String> iter = extra.keys(); Iterator<String> iter = extra.keys();
Bundle extras = new Bundle();
while (iter.hasNext()) { while (iter.hasNext()) {
String key = iter.next(); String key = iter.next();
Object value = extra.get(key); Object value = extra.get(key);
if (value instanceof Boolean boolExtra) { if (!BundleUtils.addToBundle(extras, key, value)) {
in.putExtra(key, boolExtra);
} else if (value instanceof Integer integerExtra) {
in.putExtra(key, integerExtra);
} else if (value instanceof Long longExtra) {
in.putExtra(key, longExtra);
} else if (value instanceof Double doubleExtra) {
in.putExtra(key, doubleExtra);
} else {
in.putExtra(key, value.toString()); in.putExtra(key, value.toString());
} }
} }
in.putExtras(extras);
} }
LOG.info("Executing intent:\n\t" + String.valueOf(in) + "\n\tTargeting: " + target); LOG.info("Executing intent:\n\t" + String.valueOf(in) + "\n\tTargeting: " + target);
//GB.toast(getContext(), String.valueOf(in), Toast.LENGTH_LONG, GB.INFO); //GB.toast(getContext(), String.valueOf(in), Toast.LENGTH_LONG, GB.INFO);
@@ -0,0 +1,275 @@
/* Copyright (C) 2026 Thomas Kuehne
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.util;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcelable;
import android.util.Size;
import android.util.SizeF;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
public final class BundleUtils {
private static final Logger LOG = LoggerFactory.getLogger(BundleUtils.class);
private BundleUtils() {
throw new UnsupportedOperationException("static utility class");
}
/// Put the {@code value} into the {@code bundle}. Supports simple {@link JSONObject} as well as
/// non-empty {@link JSONArray} that contain exclusively one of {@link Boolean}, {@link Double},
/// {@link Integer}, {@link Long}, or {@link String}.
///
/// @return {@code true} if the {@code value} was actually put into the {@code bundle}
public static boolean addToBundle(@NonNull final Bundle bundle,
@NonNull final String key,
@Nullable final Object value) {
if (value == null || JSONObject.NULL.equals(value)) {
bundle.putString(key, null);
return true;
}
if (value instanceof final Boolean extra) {
bundle.putBoolean(key, extra);
return true;
}
if (value instanceof final boolean[] extra) {
bundle.putBooleanArray(key, extra);
return true;
}
if (value instanceof final Byte extra) {
bundle.putByte(key, extra);
return true;
}
if (value instanceof final byte[] extra) {
bundle.putByteArray(key, extra);
return true;
}
if (value instanceof final Short extra) {
bundle.putShort(key, extra);
return true;
}
if (value instanceof final short[] extra) {
bundle.putShortArray(key, extra);
return true;
}
if (value instanceof final Character extra) {
bundle.putChar(key, extra);
return true;
}
if (value instanceof final char[] extra) {
bundle.putCharArray(key, extra);
return true;
}
if (value instanceof final Integer extra) {
bundle.putInt(key, extra);
return true;
}
if (value instanceof final int[] extra) {
bundle.putIntArray(key, extra);
return true;
}
if (value instanceof final Long extra) {
bundle.putLong(key, extra);
return true;
}
if (value instanceof final long[] extra) {
bundle.putLongArray(key, extra);
return true;
}
if (value instanceof final Float extra) {
bundle.putFloat(key, extra);
return true;
}
if (value instanceof final float[] extra) {
bundle.putFloatArray(key, extra);
return true;
}
if (value instanceof final Double extra) {
bundle.putDouble(key, extra);
return true;
}
if (value instanceof final double[] extra) {
bundle.putDoubleArray(key, extra);
return true;
}
if (value instanceof final String extra) {
bundle.putString(key, extra);
return true;
}
if (value instanceof final String[] extra) {
bundle.putStringArray(key, extra);
return true;
}
if (value instanceof final CharSequence extra) {
bundle.putCharSequence(key, extra);
return true;
}
if (value instanceof final CharSequence[] extra) {
bundle.putCharSequenceArray(key, extra);
return true;
}
if (value instanceof final Bundle extra) {
bundle.putBundle(key, extra);
return true;
}
if (value instanceof final SizeF extra) {
bundle.putSizeF(key, extra);
return true;
}
if (value instanceof final IBinder extra) {
bundle.putBinder(key, extra);
return true;
}
if (value instanceof final Parcelable extra) {
bundle.putParcelable(key, extra);
return true;
}
if (value instanceof final Parcelable[] extra) {
bundle.putParcelableArray(key, extra);
return true;
}
if (value instanceof final Serializable extra) {
bundle.putSerializable(key, extra);
return true;
}
if (value instanceof final Size extra) {
bundle.putSize(key, extra);
return true;
}
if (value instanceof final JSONArray jsonArray) {
try {
if (jsonArray.length() < 1) {
LOG.warn("empty array '{}' is unsupported", key);
return false;
}
Object first = jsonArray.get(0);
if (first instanceof Boolean) {
boolean[] array = new boolean[jsonArray.length()];
for (int arrayIndex = 0; arrayIndex < jsonArray.length(); arrayIndex++) {
Object element = jsonArray.get(arrayIndex);
if (element instanceof final Boolean x) {
array[arrayIndex] = x;
} else {
LOG.warn("boolean array '{}' contains unsupported value '{}' at index {}",
key, element, arrayIndex);
return false;
}
}
bundle.putBooleanArray(key, array);
return true;
}
if (first instanceof Double) {
double[] array = new double[jsonArray.length()];
for (int arrayIndex = 0; arrayIndex < jsonArray.length(); arrayIndex++) {
Object element = jsonArray.get(arrayIndex);
if (element instanceof final Double x) {
array[arrayIndex] = x;
} else {
LOG.warn("double array '{}' contains unsupported value '{}' at index {}",
key, element, arrayIndex);
return false;
}
}
bundle.putDoubleArray(key, array);
return true;
}
if (first instanceof Integer || first instanceof Long) {
long[] array = new long[jsonArray.length()];
for (int arrayIndex = 0; arrayIndex < jsonArray.length(); arrayIndex++) {
Object element = jsonArray.get(arrayIndex);
if (element instanceof final Long x) {
array[arrayIndex] = x;
} else if (element instanceof final Integer x) {
array[arrayIndex] = x.longValue();
} else {
LOG.warn("long array '{}' contains unsupported value '{}' at index {}",
key, element, arrayIndex);
return false;
}
}
bundle.putLongArray(key, array);
return true;
}
if (first instanceof String) {
String[] array = new String[jsonArray.length()];
for (int arrayIndex = 0; arrayIndex < jsonArray.length(); arrayIndex++) {
Object element = jsonArray.get(arrayIndex);
if (element instanceof final String x) {
array[arrayIndex] = x;
} else if (element == null) {
array[arrayIndex] = null;
} else {
LOG.warn("String array '{}' contains unsupported value '{}' at index {}",
key, element, arrayIndex);
return false;
}
}
bundle.putStringArray(key, array);
return true;
}
} catch (final JSONException exception) {
LOG.warn("failed to add JSONArray: {}", exception.getLocalizedMessage(), exception);
return false;
}
}
LOG.warn("failed to add {}: {}", value.getClass(), value);
return false;
}
}
@@ -0,0 +1,162 @@
/* Copyright (C) 2026 Thomas Kuehne
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.util;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static nodomain.freeyourgadget.gadgetbridge.util.BundleUtils.addToBundle;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.Size;
import android.util.SizeF;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Test;
import java.io.Serial;
import java.io.Serializable;
import java.nio.CharBuffer;
import nodomain.freeyourgadget.gadgetbridge.test.TestBase;
public class BundleUtilsTest extends TestBase {
@Test
public void testCommonTypes() {
Object[] values = {
null,
true,
new boolean[]{true, false},
(byte) 12,
new byte[]{12, 23},
(short) 1234,
new short[]{1234, 2345},
(char) 1234,
new char[]{1234, 2345},
123456,
new int[]{123456},
123456789L,
new long[]{123456789L},
1.1f,
new float[]{1.1f, 1.2f},
2.1,
new double[]{2.1, 2.2},
"abc",
new String[]{"abc"},
Uri.parse("http://example.com"),
new Parcelable[]{Uri.parse("http://example.com")},
CharBuffer.wrap("abc"),
new CharBuffer[]{CharBuffer.wrap("abc")},
new Serializable() {
@Serial
private static final long serialVersionUID = 8578229259758102775L;
},
new Bundle(),
new SizeF(1.2f, 3.4f),
new Size(1, 3),
};
for (int i = 0; i < values.length; i++) {
Bundle bundle = new Bundle();
Object value = values[i];
assertTrue("add[" + i + "] " + value, addToBundle(bundle, "key", value));
Object actual = bundle.get("key");
assertEquals("check[" + i + "]", value, actual);
assertEquals(1, bundle.size());
}
{
Bundle bundle = new Bundle();
assertFalse(addToBundle(bundle, "object", new Object()));
assertEquals(0, bundle.size());
}
}
@Test
public void testJson() throws JSONException {
JSONObject[] objects = {
new JSONObject().put("key", true),
new JSONObject().put("key", false),
new JSONObject().put("key", Integer.MAX_VALUE),
new JSONObject().put("key", Long.MAX_VALUE),
new JSONObject().put("key", 1.2),
new JSONObject().put("key", "xyz"),
new JSONObject("{\"key\": null }"),
};
for (int i = 0; i < objects.length; i++) {
Bundle bundle = new Bundle();
JSONObject object = objects[i];
Object value = object.get("key");
assertTrue("add[" + i + "]: " + object, addToBundle(bundle, "key", value));
Object actual = bundle.get("key");
if(value == JSONObject.NULL){
value = null;
}
assertEquals("check[" + i + "]: " + object, value, actual);
assertEquals(1, bundle.size());
}
{
Bundle bundle = new Bundle();
Object value = new JSONObject("{ \"key\" : [false, true] }").get("key");
assertTrue(addToBundle(bundle, "key", value));
Object actual = bundle.get("key");
assertArrayEquals(new boolean[]{false, true}, (boolean[]) actual);
assertEquals(1, bundle.size());
}
{
Bundle bundle = new Bundle();
Object value = new JSONObject("{ \"key\" : [123, 9223372036854775807] }").get("key");
assertTrue(addToBundle(bundle, "key", value));
Object actual = bundle.get("key");
assertArrayEquals(new long[]{123L, 9223372036854775807L}, (long[]) actual);
assertEquals(1, bundle.size());
}
{
Bundle bundle = new Bundle();
Object value = new JSONObject("{ \"key\" : [1.2, 3.4] }").get("key");
assertTrue(addToBundle(bundle, "key", value));
Object actual = bundle.get("key");
assertArrayEquals(new double[]{1.2, 3.4}, (double[]) actual, 0.001);
assertEquals(1, bundle.size());
}
{
Bundle bundle = new Bundle();
Object value = new JSONObject("{ \"key\" : [\"123\", \"abc\"] }").get("key");
assertTrue(addToBundle(bundle, "key", value));
Object actual = bundle.get("key");
assertArrayEquals(new String[]{"123", "abc"}, (String[]) actual);
assertEquals(1, bundle.size());
}
{
Bundle bundle = new Bundle();
Object value = new JSONObject("{ \"key\" : [] }").get("key");
assertFalse(addToBundle(bundle, "key", value));
assertEquals(0, bundle.size());
}
}
}