1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-25 03:16:51 +01:00

Test Device: Add fake features and data

This commit is contained in:
José Rebelo 2024-02-16 22:56:27 +00:00
parent 6a576202a9
commit a3bd7a297a
14 changed files with 1608 additions and 75 deletions

View File

@ -0,0 +1,25 @@
/* Copyright (C) 2024 José Rebelo
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.devices.test;
public class TestDeviceConst {
public static final String PREF_TEST_FEATURES = "test_features";
private TestDeviceConst() {
// utility class
}
}

View File

@ -23,113 +23,596 @@ import android.net.Uri;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.apache.commons.lang3.ArrayUtils;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.GBException; import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AppManagerActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.capabilities.HeartRateCapability;
import nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabilityImpl;
import nodomain.freeyourgadget.gadgetbridge.capabilities.widgets.WidgetManager;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.roidmi.RoidmiConst;
import nodomain.freeyourgadget.gadgetbridge.devices.test.activity.TestActivitySummaryParser;
import nodomain.freeyourgadget.gadgetbridge.devices.test.samples.TestPaiSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.test.samples.TestSpo2SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.test.samples.TestStressSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.test.samples.TestSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.test.samples.TestTemperatureSampleProvider;
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.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate; import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.model.AbstractNotificationPattern;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample;
import nodomain.freeyourgadget.gadgetbridge.model.PaiSample;
import nodomain.freeyourgadget.gadgetbridge.model.SleepRespiratoryRateSample;
import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.unknown.UnknownDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.ServiceDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.test.TestDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class TestDeviceCoordinator extends AbstractDeviceCoordinator { public class TestDeviceCoordinator extends AbstractDeviceCoordinator {
@Override @Override
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException { protected void deleteDevice(@NonNull final GBDevice gbDevice,
@NonNull final Device device,
} @NonNull final DaoSession session) throws GBException {
// Nothing to do
@NonNull
@Override
public boolean supports(GBDeviceCandidate candidate) {
return false;
}
@Nullable
@Override
public Class<? extends Activity> getPairingActivity() {
return null;
} }
@Override @Override
public boolean supportsActivityDataFetching() { public boolean supports(final GBDeviceCandidate candidate) {
return false;
}
@Override
public boolean supportsActivityTracking() {
return false;
}
@Override
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
return null;
}
@Override
public InstallHandler findInstallHandler(Uri uri, Context context) {
return null;
}
@Override
public boolean supportsScreenshots() {
return false;
}
@Override
public int getAlarmSlotCount(GBDevice device) {
return 0;
}
@Override
public boolean supportsSmartWakeup(GBDevice device) {
return false; return false;
} }
@Override @Override
public String getManufacturer() { public String getManufacturer() {
return "Test"; return "Gadgetbridge";
}
@Override
public boolean supportsAppsManagement(GBDevice device) {
return false;
}
@Override
public Class<? extends Activity> getAppsManagementActivity() {
return null;
}
@Override
public boolean supportsCalendarEvents() {
return false;
}
@Override
public boolean supportsRealtimeData() {
return false;
}
@Override
public boolean supportsFindDevice() {
return false;
} }
@NonNull @NonNull
@Override @Override
public Class<? extends DeviceSupport> getDeviceSupportClass() { public Class<? extends DeviceSupport> getDeviceSupportClass() {
return UnknownDeviceSupport.class; return TestDeviceSupport.class;
} }
@Override @Override
public int getDeviceNameResource() { public int getDeviceNameResource() {
return R.string.devicetype_test; return R.string.devicetype_test;
} }
@Override
public SampleProvider<? extends ActivitySample> getSampleProvider(final GBDevice device, final DaoSession session) {
return supportsActivityTracking() ? new TestSampleProvider(device, session) : super.getSampleProvider(device, session);
}
@Override
public TimeSampleProvider<? extends StressSample> getStressSampleProvider(final GBDevice device, final DaoSession session) {
return supportsStressMeasurement() ? new TestStressSampleProvider() : super.getStressSampleProvider(device, session);
}
@Override
public int[] getStressRanges() {
// TODO getStressRanges
return super.getStressRanges();
}
@Override
public TimeSampleProvider<? extends TemperatureSample> getTemperatureSampleProvider(final GBDevice device, final DaoSession session) {
return new TestTemperatureSampleProvider(); // TODO supportsTemperature
}
@Override
public TimeSampleProvider<? extends Spo2Sample> getSpo2SampleProvider(final GBDevice device, final DaoSession session) {
return supportsSpo2() ? new TestSpo2SampleProvider() : super.getSpo2SampleProvider(device, session);
}
@Override
public TimeSampleProvider<? extends HeartRateSample> getHeartRateMaxSampleProvider(final GBDevice device, final DaoSession session) {
// TODO getHeartRateMaxSampleProvider
return super.getHeartRateMaxSampleProvider(device, session);
}
@Override
public TimeSampleProvider<? extends HeartRateSample> getHeartRateRestingSampleProvider(final GBDevice device, final DaoSession session) {
// TODO getHeartRateRestingSampleProvider
return super.getHeartRateRestingSampleProvider(device, session);
}
@Override
public TimeSampleProvider<? extends HeartRateSample> getHeartRateManualSampleProvider(final GBDevice device, final DaoSession session) {
// TODO getHeartRateManualSampleProvider
return super.getHeartRateManualSampleProvider(device, session);
}
@Override
public TimeSampleProvider<? extends PaiSample> getPaiSampleProvider(final GBDevice device, final DaoSession session) {
return supportsPai() ? new TestPaiSampleProvider() : super.getPaiSampleProvider(device, session);
}
@Override
public TimeSampleProvider<? extends SleepRespiratoryRateSample> getSleepRespiratoryRateSampleProvider(final GBDevice device, final DaoSession session) {
// TODO getHeartRateManualSampleProvider
return super.getSleepRespiratoryRateSampleProvider(device, session);
}
@Nullable
@Override
public ActivitySummaryParser getActivitySummaryParser(final GBDevice device) {
return supportsActivityTracks() ? new TestActivitySummaryParser() : super.getActivitySummaryParser(device);
}
@Override
public File getAppCacheDir() throws IOException {
return new File(FileUtils.getExternalFilesDir(), "test-app-cache");
}
@Override
public String getAppCacheSortFilename() {
return "test-app-cache-order.txt";
}
@Override
public String getAppFileExtension() {
return ".txt";
}
@Override
public boolean supportsAppListFetching() {
return supports(getTestDevice(), TestFeature.APP_LIST_FETCHING);
}
@Override
public boolean supportsFlashing() {
return supports(getTestDevice(), TestFeature.FLASHING);
}
@Nullable
@Override
public InstallHandler findInstallHandler(final Uri uri, final Context context) {
// TODO findInstallHandler?
return super.findInstallHandler(uri, context);
}
@Override
public boolean supportsScreenshots() {
return supports(getTestDevice(), TestFeature.SCREENSHOTS);
}
@Override
public int getAlarmSlotCount(final GBDevice device) {
return super.getAlarmSlotCount(device);
}
@Override
public boolean supportsSmartWakeup(final GBDevice device) {
return supports(getTestDevice(), TestFeature.SMART_WAKEUP);
}
@Override
public boolean supportsAppReordering() {
return supports(getTestDevice(), TestFeature.APP_REORDERING);
}
@Override
public boolean supportsAppsManagement(final GBDevice device) {
return supports(getTestDevice(), TestFeature.APPS_MANAGEMENT);
}
@Override
public boolean supportsCachedAppManagement(final GBDevice device) {
return supports(getTestDevice(), TestFeature.CACHED_APP_MANAGEMENT);
}
@Override
public boolean supportsInstalledAppManagement(final GBDevice device) {
return supports(getTestDevice(), TestFeature.INSTALLED_APP_MANAGEMENT);
}
@Override
public boolean supportsWatchfaceManagement(final GBDevice device) {
return supports(getTestDevice(), TestFeature.WATCHFACE_MANAGEMENT);
}
@Nullable
@Override
public Class<? extends Activity> getAppsManagementActivity() {
return AppManagerActivity.class;
}
@Nullable
@Override
public Class<? extends Activity> getWatchfaceDesignerActivity() {
// TODO getWatchfaceDesignerActivity
return super.getWatchfaceDesignerActivity();
}
@Override
public boolean supportsCalendarEvents() {
return supports(getTestDevice(), TestFeature.CALENDAR_EVENTS);
}
@Override
public boolean supportsActivityDataFetching() {
return supports(getTestDevice(), TestFeature.ACTIVITY_DATA_FETCHING);
}
@Override
public boolean supportsActivityTracking() {
return supports(getTestDevice(), TestFeature.ACTIVITY_TRACKING);
}
@Override
public boolean supportsActivityTracks() {
return supports(getTestDevice(), TestFeature.ACTIVITY_TRACKS);
}
@Override
public boolean supportsStressMeasurement() {
return supports(getTestDevice(), TestFeature.STRESS_MEASUREMENT);
}
@Override
public boolean supportsSpo2() {
return supports(getTestDevice(), TestFeature.SPO2);
}
@Override
public boolean supportsHeartRateStats() {
return supports(getTestDevice(), TestFeature.HEART_RATE_STATS);
}
@Override
public boolean supportsPai() {
return supports(getTestDevice(), TestFeature.PAI);
}
@Override
public int getPaiName() {
return R.string.menuitem_pai;
}
@Override
public boolean supportsPaiTime() {
return supports(getTestDevice(), TestFeature.PAI_TIME);
}
@Override
public boolean supportsSleepRespiratoryRate() {
return supports(getTestDevice(), TestFeature.SLEEP_RESPIRATORY_RATE);
}
@Override
public boolean supportsAlarmSnoozing() {
return supports(getTestDevice(), TestFeature.ALARM_SNOOZING);
}
@Override
public boolean supportsAlarmTitle(final GBDevice device) {
return supports(getTestDevice(), TestFeature.ALARM_TITLE);
}
@Override
public int getAlarmTitleLimit(final GBDevice device) {
return supports(getTestDevice(), TestFeature.ALARM_TITLE_LIMIT) ? 10 : -1;
}
@Override
public boolean supportsAlarmDescription(final GBDevice device) {
return supports(getTestDevice(), TestFeature.ALARM_DESCRIPTION);
}
@Override
public boolean supportsMusicInfo() {
return supports(getTestDevice(), TestFeature.MUSIC_INFO);
}
@Override
public boolean supportsLedColor() {
return supports(getTestDevice(), TestFeature.LED_COLOR);
}
@Override
public int getMaximumReminderMessageLength() {
// TODO getMaximumReminderMessageLength
return 16;
}
@Override
public int getReminderSlotCount(final GBDevice device) {
return supports(getTestDevice(), TestFeature.REMINDERS) ? 3 : 0;
}
@Override
public int getCannedRepliesSlotCount(final GBDevice device) {
return supports(getTestDevice(), TestFeature.CANNED_REPLIES) ? 3 : 0;
}
@Override
public int getWorldClocksSlotCount() {
return supports(getTestDevice(), TestFeature.WORLD_CLOCKS) ? 3 : 0;
}
@Override
public int getWorldClocksLabelLength() {
// TODO getWorldClocksLabelLength
return 10;
}
@Override
public boolean supportsDisabledWorldClocks() {
return supports(getTestDevice(), TestFeature.DISABLED_WORLD_CLOCKS);
}
@Override
public int getContactsSlotCount(final GBDevice device) {
return supports(getTestDevice(), TestFeature.CONTACTS) ? 3 : 0;
}
@Override
public boolean supportsRgbLedColor() {
return supports(getTestDevice(), TestFeature.RGB_LED_COLOR);
}
@NonNull
@Override
public int[] getColorPresets() {
return RoidmiConst.COLOR_PRESETS;
}
@Override
public boolean supportsHeartRateMeasurement(final GBDevice device) {
return supports(getTestDevice(), TestFeature.HEART_RATE_MEASUREMENT);
}
@Override
public boolean supportsManualHeartRateMeasurement(final GBDevice device) {
return supports(getTestDevice(), TestFeature.MANUAL_HEART_RATE_MEASUREMENT);
}
@Override
public boolean supportsRealtimeData() {
return supports(getTestDevice(), TestFeature.REALTIME_DATA);
}
@Override
public boolean supportsRemSleep() {
return supports(getTestDevice(), TestFeature.REM_SLEEP);
}
@Override
public boolean supportsWeather() {
return supports(getTestDevice(), TestFeature.WEATHER);
}
@Override
public boolean supportsFindDevice() {
return supports(getTestDevice(), TestFeature.FIND_DEVICE);
}
@Override
public boolean supportsUnicodeEmojis() {
return supports(getTestDevice(), TestFeature.UNICODE_EMOJIS);
}
@Override
public int[] getSupportedDeviceSpecificConnectionSettings() {
return super.getSupportedDeviceSpecificConnectionSettings();
}
@Override
public int[] getSupportedDeviceSpecificApplicationSettings() {
return new int[]{
R.xml.devicesettings_custom_deviceicon,
};
}
@Override
public int[] getSupportedDeviceSpecificSettings(final GBDevice device) {
final List<Integer> settings = new ArrayList<>();
settings.add(R.xml.devicesettings_header_apps);
settings.add(R.xml.devicesettings_loyalty_cards);
if (getWorldClocksSlotCount() > 0) {
settings.add(R.xml.devicesettings_header_time);
settings.add(R.xml.devicesettings_world_clocks);
}
if (getContactsSlotCount(device) > 0) {
settings.add(R.xml.devicesettings_header_other);
settings.add(R.xml.devicesettings_contacts);
}
settings.add(R.xml.devicesettings_header_developer);
settings.add(R.xml.devicesettings_test_features);
return ArrayUtils.toPrimitive(settings.toArray(new Integer[0]));
}
@Override
public int[] getSupportedDeviceSpecificAuthenticationSettings() {
return new int[]{R.xml.devicesettings_pairingkey};
}
@Override
public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
return new TestDeviceSpecificSettingsCustomizer();
}
@Override
public String[] getSupportedLanguageSettings(final GBDevice device) {
return new String[]{
"auto",
"en_US",
};
}
@Nullable
@Override
public Class<? extends Activity> getCalibrationActivity() {
// TODO getCalibrationActivity
return super.getCalibrationActivity();
}
@Override
public int getBatteryCount() {
return supports(getTestDevice(), TestFeature.BATTERIES_MULTIPLE) ? 3 : 1;
}
@Override
public BatteryConfig[] getBatteryConfig() {
if (getBatteryCount() == 1) {
return super.getBatteryConfig();
}
final BatteryConfig[] ret = new BatteryConfig[getBatteryCount()];
for (int i = 0; i < getBatteryCount(); i++) {
ret[i] = new BatteryConfig(i, R.drawable.ic_battery_full, R.string.battery);
}
return ret;
}
@Override
public boolean supportsPowerOff() {
return supports(getTestDevice(), TestFeature.POWER_OFF);
}
@Override
public PasswordCapabilityImpl.Mode getPasswordCapability() {
return super.getPasswordCapability();
}
@Override
public List<HeartRateCapability.MeasurementInterval> getHeartRateMeasurementIntervals() {
return super.getHeartRateMeasurementIntervals();
}
@Override
public boolean supportsWidgets(final GBDevice device) {
return supports(getTestDevice(), TestFeature.WIDGETS);
}
@Nullable
@Override
public WidgetManager getWidgetManager(final GBDevice device) {
return super.getWidgetManager(device);
}
@Override
public boolean supportsNavigation() {
return supports(getTestDevice(), TestFeature.NAVIGATION);
}
@Override
public EnumSet<ServiceDeviceSupport.Flags> getInitialFlags() {
return EnumSet.noneOf(ServiceDeviceSupport.Flags.class);
}
@Override
public int getDefaultIconResource() {
// TODO getDefaultIconResource
return super.getDefaultIconResource();
}
@Override
public int getDisabledIconResource() {
// TODO getDisabledIconResource
return super.getDisabledIconResource();
}
@Override
public boolean supportsNotificationVibrationPatterns() {
return supports(getTestDevice(), TestFeature.NOTIFICATION_VIBRATION_PATTERNS);
}
@Override
public boolean supportsNotificationVibrationRepetitionPatterns() {
return supports(getTestDevice(), TestFeature.NOTIFICATION_VIBRATION_REPETITION_PATTERNS);
}
@Override
public boolean supportsNotificationLedPatterns() {
return supports(getTestDevice(), TestFeature.NOTIFICATION_LED_PATTERNS);
}
@Override
public AbstractNotificationPattern[] getNotificationVibrationPatterns() {
// TODO getNotificationVibrationPatterns
return new AbstractNotificationPattern[0];
}
@Override
public AbstractNotificationPattern[] getNotificationVibrationRepetitionPatterns() {
// TODO getNotificationVibrationRepetitionPatterns
return new AbstractNotificationPattern[0];
}
@Override
public AbstractNotificationPattern[] getNotificationLedPatterns() {
// TODO getNotificationLedPatterns
return new AbstractNotificationPattern[0];
}
@Override
public boolean validateAuthKey(final String authKey) {
return authKey.equals("hunter2");
}
public boolean supports(final GBDevice device, final TestFeature feature) {
if (device == null) {
return false;
}
final Set<String> features = new HashSet<>(getPrefs(device).getStringSet(TestDeviceConst.PREF_TEST_FEATURES, Collections.emptySet()));
// if nothing enable, enable everything
return features.isEmpty() || features.contains(feature.name());
}
@Nullable
private GBDevice getTestDevice() {
// HACK: Avoid the refactor of adding a device as a parameter on all functions
// This means that it's only possible to have a single test device
if (GBApplication.app() == null || GBApplication.app().getDeviceManager() == null) {
return null;
}
final List<GBDevice> devices = GBApplication.app().getDeviceManager().getDevices();
for (GBDevice device : devices) {
if (DeviceType.TEST == device.getType()) {
return device;
}
}
return null;
}
protected static Prefs getPrefs(final GBDevice device) {
return new Prefs(GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()));
}
} }

View File

@ -0,0 +1,47 @@
/* Copyright (C) 2024 José Rebelo
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.devices.test;
import java.util.Random;
/**
* Utility class to try and get deterministic random values across multiple users. This is not efficient
* and should definitely be improved.
*/
public class TestDeviceRand {
public static final long BASE_TIMESTAMP = 1420499943000L;
private TestDeviceRand() {
// utility class
}
public static int randInt(final long ts, final int min, final int max) {
return new Random(ts - BASE_TIMESTAMP).nextInt((max - min) + 1) + min;
}
public static long randLong(final long ts, final long min, final long max) {
return (long) (new Random(ts - BASE_TIMESTAMP).nextFloat() * (max - min) + min);
}
public static float randFloat(final long ts, final float min, final float max) {
return new Random(ts - BASE_TIMESTAMP).nextFloat() * (max - min) + min;
}
public static boolean randBool(final long ts, final float pTrue) {
return new Random(ts - BASE_TIMESTAMP).nextFloat() < pTrue;
}
}

View File

@ -0,0 +1,84 @@
/* Copyright (C) 2024 José Rebelo
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.devices.test;
import android.os.Parcel;
import androidx.preference.MultiSelectListPreference;
import androidx.preference.Preference;
import java.util.Collections;
import java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class TestDeviceSpecificSettingsCustomizer implements DeviceSpecificSettingsCustomizer {
@Override
public void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler) {
if (TestDeviceConst.PREF_TEST_FEATURES.equals(preference.getKey())) {
handler.getDevice().sendDeviceUpdateIntent(handler.getContext());
}
}
@Override
public void customizeSettings(final DeviceSpecificSettingsHandler handler, final Prefs prefs) {
final Preference pref = handler.findPreference(TestDeviceConst.PREF_TEST_FEATURES);
if (pref == null) {
return;
}
// Populate the preference directly from the enum
final CharSequence[] entries = new CharSequence[TestFeature.values().length];
final CharSequence[] values = new CharSequence[TestFeature.values().length];
for (int i = 0; i < TestFeature.values().length; i++) {
entries[i] = TestFeature.values()[i].name();
values[i] = TestFeature.values()[i].name();
}
if (pref instanceof MultiSelectListPreference) {
((MultiSelectListPreference) pref).setEntries(entries);
((MultiSelectListPreference) pref).setEntryValues(values);
}
}
@Override
public Set<String> getPreferenceKeysWithSummary() {
return Collections.emptySet();
}
public static final Creator<TestDeviceSpecificSettingsCustomizer> CREATOR = new Creator<TestDeviceSpecificSettingsCustomizer>() {
@Override
public TestDeviceSpecificSettingsCustomizer createFromParcel(final Parcel in) {
return new TestDeviceSpecificSettingsCustomizer();
}
@Override
public TestDeviceSpecificSettingsCustomizer[] newArray(final int size) {
return new TestDeviceSpecificSettingsCustomizer[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(final Parcel dest, final int flags) {
}
}

View File

@ -0,0 +1,66 @@
/* Copyright (C) 2024 José Rebelo
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.devices.test;
public enum TestFeature {
ACTIVITY_DATA_FETCHING,
ACTIVITY_TRACKING,
ACTIVITY_TRACKS,
ALARM_DESCRIPTION,
ALARM_SNOOZING,
ALARM_TITLE_LIMIT,
ALARM_TITLE,
APP_LIST_FETCHING,
APP_REORDERING,
APPS_MANAGEMENT,
BATTERIES_MULTIPLE,
CACHED_APP_MANAGEMENT,
CALENDAR_EVENTS,
CANNED_REPLIES,
CONTACTS,
DISABLED_WORLD_CLOCKS,
FIND_DEVICE,
FLASHING,
FM_FREQUENCY,
HEART_RATE_MEASUREMENT,
HEART_RATE_STATS,
INSTALLED_APP_MANAGEMENT,
LED_COLOR,
MANUAL_HEART_RATE_MEASUREMENT,
MUSIC_INFO,
NAVIGATION,
NOTIFICATION_LED_PATTERNS,
NOTIFICATION_VIBRATION_PATTERNS,
NOTIFICATION_VIBRATION_REPETITION_PATTERNS,
PAI_TIME,
PAI,
POWER_OFF,
REALTIME_DATA,
REM_SLEEP,
REMINDERS,
RGB_LED_COLOR,
SCREENSHOTS,
SLEEP_RESPIRATORY_RATE,
SMART_WAKEUP,
SPO2,
STRESS_MEASUREMENT,
UNICODE_EMOJIS,
WATCHFACE_MANAGEMENT,
WEATHER,
WIDGETS,
WORLD_CLOCKS,
}

View File

@ -0,0 +1,27 @@
/* Copyright (C) 2024 José Rebelo
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.devices.test.activity;
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
public class TestActivitySummaryParser implements ActivitySummaryParser {
@Override
public BaseActivitySummary parseBinaryData(final BaseActivitySummary summary) {
return summary;
}
}

View File

@ -0,0 +1,163 @@
/* Copyright (C) 2024 José Rebelo
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.devices.test.samples;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.test.TestDeviceRand;
import nodomain.freeyourgadget.gadgetbridge.model.PaiSample;
public class TestPaiSampleProvider implements TimeSampleProvider<PaiSample> {
@NonNull
@Override
public List<PaiSample> getAllSamples(final long timestampFrom, final long timestampTo) {
final List<PaiSample> samples = new ArrayList<>();
int paiLow = TestDeviceRand.randInt(timestampFrom, 0, 15);
int paiMod = TestDeviceRand.randInt(timestampFrom, 0, 15);
int paiHigh = TestDeviceRand.randInt(timestampFrom, 0, 15);
int paiTotal = paiLow + paiMod + paiHigh;
for (long ts = timestampFrom; ts < timestampTo; ts += 12 * 60 * 60 * 1000L) {
if (TestDeviceRand.randBool(ts, 0.75f)) {
samples.add(new TestPaiSample(
ts,
paiLow,
paiMod,
paiHigh,
paiTotal
));
}
paiTotal += paiLow + paiMod + paiHigh;
paiLow += TestDeviceRand.randInt(ts, - paiLow, 15 - paiLow);
paiMod += TestDeviceRand.randInt(ts, - paiMod, 15 - paiMod);
paiHigh += TestDeviceRand.randInt(ts, - paiHigh, 15 - paiHigh);
}
return samples;
}
@Override
public void addSample(final PaiSample timeSample) {
throw new UnsupportedOperationException("read-only sample provider");
}
@Override
public void addSamples(final List<PaiSample> timeSamples) {
throw new UnsupportedOperationException("read-only sample provider");
}
@Override
public PaiSample createSample() {
throw new UnsupportedOperationException("read-only sample provider");
}
@Nullable
@Override
public PaiSample getLatestSample() {
final long ts = System.currentTimeMillis();
return new TestPaiSample(
ts - TestDeviceRand.randLong(ts, 10 * 1000L, 2 * 60 * 60 * 1000L),
TestDeviceRand.randInt(ts, 85, 99),
TestDeviceRand.randInt(ts, 85, 99),
TestDeviceRand.randInt(ts, 85, 99),
TestDeviceRand.randInt(ts, 85, 99)
);
}
@Nullable
@Override
public PaiSample getFirstSample() {
return new TestPaiSample(
TestDeviceRand.BASE_TIMESTAMP,
TestDeviceRand.randInt(TestDeviceRand.BASE_TIMESTAMP, 0, 15),
TestDeviceRand.randInt(TestDeviceRand.BASE_TIMESTAMP, 0, 15),
TestDeviceRand.randInt(TestDeviceRand.BASE_TIMESTAMP, 0, 15),
TestDeviceRand.randInt(TestDeviceRand.BASE_TIMESTAMP, 70, 90)
);
}
public static class TestPaiSample implements PaiSample {
private final long timestamp;
private final int paiLow;
private final int paiModerate;
private final int paiHigh;
private final int paiTotal;
public TestPaiSample(final long timestamp,
final int low,
final int moderate,
final int high,
final int total) {
this.timestamp = timestamp;
this.paiLow = low;
this.paiModerate = moderate;
this.paiHigh = high;
this.paiTotal = total;
}
@Override
public long getTimestamp() {
return timestamp;
}
@Override
public float getPaiLow() {
return paiLow;
}
@Override
public float getPaiModerate() {
return paiModerate;
}
@Override
public float getPaiHigh() {
return paiHigh;
}
@Override
public int getTimeLow() {
return paiLow;
}
@Override
public int getTimeModerate() {
return paiModerate;
}
@Override
public int getTimeHigh() {
return paiHigh;
}
@Override
public float getPaiToday() {
return paiLow + paiModerate + paiHigh;
}
@Override
public float getPaiTotal() {
return paiTotal;
}
}
}

View File

@ -0,0 +1,241 @@
/* Copyright (C) 2024 José Rebelo
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.devices.test.samples;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.apache.commons.lang3.ArrayUtils;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.Property;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.test.TestDeviceRand;
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
public class TestSampleProvider extends AbstractSampleProvider<TestSampleProvider.TestActivitySample> {
public TestSampleProvider(final GBDevice device, final DaoSession session) {
super(device, session);
}
@Override
public AbstractDao<TestActivitySample, ?> getSampleDao() {
return null;
}
@Nullable
@Override
protected Property getRawKindSampleProperty() {
return null;
}
@NonNull
@Override
protected Property getTimestampSampleProperty() {
return null;
}
@NonNull
@Override
protected Property getDeviceIdentifierSampleProperty() {
return null;
}
@Override
public int normalizeType(final int rawType) {
return rawType;
}
@Override
public int toRawActivityKind(final int activityKind) {
return activityKind;
}
@Override
public float normalizeIntensity(final int rawIntensity) {
return rawIntensity / 100f;
}
@Override
public TestActivitySample createActivitySample() {
throw new UnsupportedOperationException("read-only sample provider");
}
@Override
protected List<TestActivitySample> getGBActivitySamples(final int timestamp_from, final int timestamp_to, final int activityType) {
final List<TestActivitySample> samples = new ArrayList<>();
int[] sleepStages = new int[] {
ActivityKind.TYPE_LIGHT_SLEEP,
ActivityKind.TYPE_DEEP_SLEEP,
};
if (getDevice().getDeviceCoordinator().supportsRemSleep()) {
sleepStages = ArrayUtils.add(sleepStages, ActivityKind.TYPE_REM_SLEEP);
}
int sleepStageCurrent = 0;
int sleepStageDirection = 1;
int sleepStageDurationRemaining = TestDeviceRand.randInt(timestamp_from * 1000L, 30, 90);
int steps = TestDeviceRand.randInt(timestamp_from * 1000L, 0, 170);
int intensity = TestDeviceRand.randInt(timestamp_from * 1000L, 0, 100);
int hr = TestDeviceRand.randInt(timestamp_from * 1000L, 90, 153);
final long bedtimeHour = TestDeviceRand.randInt(timestamp_from, 21, 22);
final long bedtimeMinute = TestDeviceRand.randInt(timestamp_from, 0, 59);
final long wakeHour = TestDeviceRand.randInt(timestamp_from, 6, 9);
final long wakeMinute = TestDeviceRand.randInt(timestamp_from, 0, 59);
final Calendar cal = GregorianCalendar.getInstance();
for (long ts = timestamp_from * 1000L; ts < timestamp_to * 1000L; ts += 60 * 1000L) {
cal.setTimeInMillis(ts);
final int h = cal.get(Calendar.HOUR_OF_DAY);
final int m = cal.get(Calendar.MINUTE);
boolean isSleep = false;
if (h == bedtimeHour) {
isSleep = m >= bedtimeMinute;
} else if (h > bedtimeHour) {
isSleep = true;
} else if (h == wakeHour) {
isSleep = m <= wakeMinute;
} else if (h < wakeHour) {
isSleep = true;
}
if (TestDeviceRand.randBool(ts, 0.75f)) {
samples.add(new TestActivitySample(
(int) (ts / 1000),
isSleep ? sleepStages[sleepStageCurrent] : ActivityKind.TYPE_UNKNOWN,
steps,
intensity,
hr
));
}
if (isSleep) {
sleepStageDurationRemaining--;
if (sleepStageDurationRemaining <= 0) {
sleepStageDurationRemaining = TestDeviceRand.randInt(ts * 1000L, 30, 90);
sleepStageCurrent += sleepStageDirection;
if (sleepStageCurrent == 0 || sleepStageCurrent == sleepStages.length - 1) {
sleepStageDirection *= -1;
}
}
}
steps += TestDeviceRand.randInt(ts, -steps, 170 - steps);
intensity += TestDeviceRand.randInt(ts, -1, 1);
hr += TestDeviceRand.randInt(ts, -2, 2);
}
return samples;
}
public class TestActivitySample extends AbstractActivitySample {
private final int timestamp;
private final int kind;
private final int steps;
private final int intensity;
private final int hr;
public TestActivitySample(final int timestamp,
final int kind,
final int steps,
final int intensity,
final int hr) {
this.timestamp = timestamp;
this.kind = kind;
this.steps = steps;
this.intensity = intensity;
this.hr = hr;
}
@Override
public SampleProvider getProvider() {
return TestSampleProvider.this;
}
@Override
public void setTimestamp(final int timestamp) {
}
@Override
public void setUserId(final long userId) {
}
@Override
public void setDeviceId(final long deviceId) {
}
@Override
public long getDeviceId() {
return 0;
}
@Override
public long getUserId() {
return 0;
}
@Override
public int getTimestamp() {
return timestamp;
}
@Override
public int getKind() {
return kind;
}
@Override
public int getRawKind() {
return kind;
}
@Override
public float getIntensity() {
return intensity / 100f;
}
@Override
public int getHeartRate() {
return hr;
}
@Override
public int getRawIntensity() {
return intensity;
}
@Override
public int getSteps() {
return steps;
}
}
}

View File

@ -0,0 +1,105 @@
/* Copyright (C) 2024 José Rebelo
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.devices.test.samples;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.test.TestDeviceRand;
import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
public class TestSpo2SampleProvider implements TimeSampleProvider<Spo2Sample> {
@NonNull
@Override
public List<Spo2Sample> getAllSamples(final long timestampFrom, final long timestampTo) {
final List<Spo2Sample> samples = new ArrayList<>();
int spo2 = TestDeviceRand.randInt(timestampFrom, 85, 99);
for (long ts = timestampFrom; ts < timestampTo; ts += 15 * 60 * 1000L) {
if (TestDeviceRand.randBool(ts, 0.3f)) {
samples.add(new TestSpo2Sample(ts, spo2));
}
spo2 += TestDeviceRand.randInt(ts, 85 - spo2, 99 - spo2);
}
return samples;
}
@Override
public void addSample(final Spo2Sample timeSample) {
throw new UnsupportedOperationException("read-only sample provider");
}
@Override
public void addSamples(final List<Spo2Sample> timeSamples) {
throw new UnsupportedOperationException("read-only sample provider");
}
@Override
public Spo2Sample createSample() {
throw new UnsupportedOperationException("read-only sample provider");
}
@Nullable
@Override
public Spo2Sample getLatestSample() {
final long ts = System.currentTimeMillis();
return new TestSpo2Sample(
ts - TestDeviceRand.randLong(ts, 10 * 1000L, 2 * 60 * 60 * 1000L),
TestDeviceRand.randInt(ts, 85, 99)
);
}
@Nullable
@Override
public Spo2Sample getFirstSample() {
return new TestSpo2Sample(
TestDeviceRand.BASE_TIMESTAMP,
TestDeviceRand.randInt(TestDeviceRand.BASE_TIMESTAMP, 85, 99)
);
}
protected static class TestSpo2Sample implements Spo2Sample {
private final long timestamp;
private final int spo2;
public TestSpo2Sample(final long timestamp, final int spo2) {
this.timestamp = timestamp;
this.spo2 = spo2;
}
@Override
public long getTimestamp() {
return timestamp;
}
@Override
public Type getType() {
return Type.UNKNOWN;
}
@Override
public int getSpo2() {
return spo2;
}
}
}

View File

@ -0,0 +1,105 @@
/* Copyright (C) 2024 José Rebelo
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.devices.test.samples;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.test.TestDeviceRand;
import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
public class TestStressSampleProvider implements TimeSampleProvider<StressSample> {
@NonNull
@Override
public List<StressSample> getAllSamples(final long timestampFrom, final long timestampTo) {
final List<StressSample> samples = new ArrayList<>();
int stress = TestDeviceRand.randInt(timestampFrom, 10, 90);
for (long ts = timestampFrom; ts < timestampTo; ts += 15 * 60 * 1000L) {
if (TestDeviceRand.randBool(ts, 0.3f)) {
samples.add(new TestStressSample(ts, stress));
}
stress += TestDeviceRand.randInt(ts, (10 - stress) / 10, (90 - stress) / 10);
}
return samples;
}
@Override
public void addSample(final StressSample timeSample) {
throw new UnsupportedOperationException("read-only sample provider");
}
@Override
public void addSamples(final List<StressSample> timeSamples) {
throw new UnsupportedOperationException("read-only sample provider");
}
@Override
public StressSample createSample() {
throw new UnsupportedOperationException("read-only sample provider");
}
@Nullable
@Override
public StressSample getLatestSample() {
final long ts = System.currentTimeMillis();
return new TestStressSample(
ts - TestDeviceRand.randLong(ts, 10 * 1000L, 2 * 60 * 60 * 1000L),
TestDeviceRand.randInt(ts, 10, 90)
);
}
@Nullable
@Override
public StressSample getFirstSample() {
return new TestStressSample(
TestDeviceRand.BASE_TIMESTAMP,
TestDeviceRand.randInt(TestDeviceRand.BASE_TIMESTAMP, 10, 90)
);
}
protected static class TestStressSample implements StressSample {
private final long timestamp;
private final int stress;
public TestStressSample(final long timestamp, final int stress) {
this.timestamp = timestamp;
this.stress = stress;
}
@Override
public long getTimestamp() {
return timestamp;
}
@Override
public Type getType() {
return Type.UNKNOWN;
}
@Override
public int getStress() {
return stress;
}
}
}

View File

@ -0,0 +1,92 @@
/* Copyright (C) 2024 José Rebelo
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.devices.test.samples;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Collections;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.test.TestDeviceRand;
import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
public class TestTemperatureSampleProvider implements TimeSampleProvider<TemperatureSample> {
@NonNull
@Override
public List<TemperatureSample> getAllSamples(final long timestampFrom, final long timestampTo) {
// TODO fake samples
return Collections.emptyList();
}
@Override
public void addSample(final TemperatureSample timeSample) {
throw new UnsupportedOperationException("read-only sample provider");
}
@Override
public void addSamples(final List<TemperatureSample> timeSamples) {
throw new UnsupportedOperationException("read-only sample provider");
}
@Override
public TemperatureSample createSample() {
throw new UnsupportedOperationException("read-only sample provider");
}
@Nullable
@Override
public TemperatureSample getLatestSample() {
// TODO
return null;
}
@Nullable
@Override
public TemperatureSample getFirstSample() {
return new TestTemperatureSample(
TestDeviceRand.BASE_TIMESTAMP,
TestDeviceRand.randFloat(TestDeviceRand.BASE_TIMESTAMP, 36f, 38f)
);
}
protected static class TestTemperatureSample implements TemperatureSample {
private final long timestamp;
private final float temperature;
public TestTemperatureSample(final long timestamp, final float temperature) {
this.timestamp = timestamp;
this.temperature = temperature;
}
@Override
public long getTimestamp() {
return timestamp;
}
@Override
public float getTemperature() {
return temperature;
}
@Override
public int getTemperatureType() {
return 0; // ?
}
}
}

View File

@ -0,0 +1,81 @@
/* Copyright (C) 2024 José Rebelo
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.service.devices.test;
import android.os.Handler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.test.TestDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.test.TestFeature;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport;
public class TestDeviceSupport extends AbstractDeviceSupport {
private static final Logger LOG = LoggerFactory.getLogger(TestDeviceSupport.class);
private final Handler handler = new Handler();
@Override
public boolean connect() {
LOG.info("Connecting");
getDevice().setState(GBDevice.State.CONNECTING);
getDevice().sendDeviceUpdateIntent(getContext());
handler.postDelayed(() -> {
LOG.info("Initialized");
if (getCoordinator().supportsLedColor()) {
getDevice().setExtraInfo("led_color", 0xff3061e3);
} else {
getDevice().setExtraInfo("led_color", null);
}
if (getCoordinator().supports(getDevice(), TestFeature.FM_FREQUENCY)) {
getDevice().setExtraInfo("fm_frequency", 90.2f);
} else {
getDevice().setExtraInfo("fm_frequency", null);
}
// TODO battery percentages
// TODO hr measurements
// TODO app list
// TODO screenshots
getDevice().setState(GBDevice.State.INITIALIZED);
getDevice().sendDeviceUpdateIntent(getContext());
}, 1000);
return true;
}
@Override
public void dispose() {
}
@Override
public boolean useAutoConnect() {
return false;
}
protected TestDeviceCoordinator getCoordinator() {
return (TestDeviceCoordinator) getDevice().getDeviceCoordinator();
}
}

View File

@ -2679,4 +2679,6 @@
<string name="pref_force_connection_type_bt_classic_value" translatable="false">BT_CLASSIC</string> <string name="pref_force_connection_type_bt_classic_value" translatable="false">BT_CLASSIC</string>
<string name="activity_info">Activity info</string> <string name="activity_info">Activity info</string>
<string name="warning_missing_notification_permission">Could not post ongoing notification due to missing permission</string> <string name="warning_missing_notification_permission">Could not post ongoing notification due to missing permission</string>
<string name="pref_test_features_title">Features</string>
<string name="pref_test_features_summary">Enabled features for this test device</string>
</resources> </resources>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<!-- the values will be populated by the TestDeviceSpecificSettingsCustomizer -->
<MultiSelectListPreference
android:defaultValue="@array/empty_array"
android:entries="@array/empty_array"
android:entryValues="@array/empty_array"
android:icon="@drawable/ic_developer_mode"
android:key="test_features"
android:summary="@string/pref_test_features_summary"
android:title="@string/pref_test_features_title" />
</androidx.preference.PreferenceScreen>