/* 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 . */
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 {
public TestSampleProvider(final GBDevice device, final DaoSession session) {
super(device, session);
}
@Override
public AbstractDao getSampleDao() {
return null;
}
@Nullable
@Override
protected Property getRawKindSampleProperty() {
return null;
}
@NonNull
@Override
protected Property getTimestampSampleProperty() {
//noinspection DataFlowIssue not database-backed
return null;
}
@NonNull
@Override
protected Property getDeviceIdentifierSampleProperty() {
//noinspection DataFlowIssue not database-backed
return null;
}
@Override
public ActivityKind normalizeType(final int rawType) {
return ActivityKind.fromCode(rawType);
}
@Override
public int toRawActivityKind(final ActivityKind activityKind) {
return activityKind.getCode();
}
@Override
public float normalizeIntensity(final int rawIntensity) {
return rawIntensity / 100f;
}
@Override
public TestActivitySample createActivitySample() {
throw new UnsupportedOperationException("read-only sample provider");
}
@Override
protected List getGBActivitySamples(final int timestamp_from, final int timestamp_to) {
final List samples = new ArrayList<>();
int[] sleepStages = new int[] {
ActivityKind.LIGHT_SLEEP.getCode(),
ActivityKind.DEEP_SLEEP.getCode(),
};
if (getDevice().getDeviceCoordinator().supportsRemSleep()) {
sleepStages = ArrayUtils.add(sleepStages, ActivityKind.REM_SLEEP.getCode());
}
int sleepStageCurrent = 0;
int sleepStageDirection = 1;
int sleepStageDurationRemaining = TestDeviceRand.randInt(timestamp_from * 1000L, 30, 90);
boolean isActive = false;
float dayActivityFactor = TestDeviceRand.randFloat(timestamp_from * 1000L, 0f, 1f);
int steps = (int) (TestDeviceRand.randInt(timestamp_from * 1000L, 0, 100) * dayActivityFactor);
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 (isSleep) {
isActive = false;
} else if (isActive) {
isActive = TestDeviceRand.randBool(ts, 0.8F);
} else {
isActive = TestDeviceRand.randBool(ts, 0.05F);
}
if (TestDeviceRand.randBool(ts, 0.85f)) {
samples.add(new TestActivitySample(
(int) (ts / 1000),
isSleep ? sleepStages[sleepStageCurrent] : ActivityKind.UNKNOWN.getCode(),
isActive ? steps : 0,
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 += (int) (TestDeviceRand.randInt(ts, -steps, 100 - steps) * dayActivityFactor);
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 ActivityKind getKind() {
return ActivityKind.fromCode(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;
}
@Override
public int getDistanceCm() {
return steps * 67;
}
@Override
public int getActiveCalories() {
return (int) Math.round(steps * 0.04);
}
}
}