mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-07-08 14:41:36 +02:00
531 lines
22 KiB
Java
531 lines
22 KiB
Java
/* Copyright (C) 2024 Damien Gaignon, Martin.JM
|
|
|
|
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.huawei;
|
|
|
|
import android.content.SharedPreferences;
|
|
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
|
|
import de.greenrobot.dao.AbstractDao;
|
|
import de.greenrobot.dao.Property;
|
|
import de.greenrobot.dao.query.QueryBuilder;
|
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
|
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
|
|
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
|
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
|
|
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
|
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
|
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiActivitySample;
|
|
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiActivitySampleDao;
|
|
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutDataSample;
|
|
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutDataSampleDao;
|
|
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySample;
|
|
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySampleDao;
|
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
|
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FitnessData;
|
|
|
|
public class HuaweiSampleProvider extends AbstractSampleProvider<HuaweiActivitySample> {
|
|
|
|
/*
|
|
* We save all data by saving a marker at the begin and end.
|
|
* Meaning of fields that are not self-explanatory:
|
|
* - `otherTimestamp`
|
|
* The timestamp of the other marker, if it's larger this is the begin, otherwise the end
|
|
* - `source`
|
|
* The source of the data, which Huawei Band message the data came from
|
|
*/
|
|
|
|
private static class RawTypes {
|
|
public static final int NOT_MEASURED = -1;
|
|
|
|
public static final int UNKNOWN = 1;
|
|
|
|
public static final int DEEP_SLEEP = 0x07;
|
|
public static final int LIGHT_SLEEP = 0x06;
|
|
}
|
|
|
|
public HuaweiSampleProvider(GBDevice device, DaoSession session) {
|
|
super(device, session);
|
|
}
|
|
|
|
@Override
|
|
public int normalizeType(int rawType) {
|
|
switch (rawType) {
|
|
case RawTypes.DEEP_SLEEP:
|
|
return ActivityKind.TYPE_DEEP_SLEEP;
|
|
case RawTypes.LIGHT_SLEEP:
|
|
return ActivityKind.TYPE_LIGHT_SLEEP;
|
|
default:
|
|
return ActivityKind.TYPE_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int toRawActivityKind(int activityKind) {
|
|
switch (activityKind) {
|
|
case ActivityKind.TYPE_DEEP_SLEEP:
|
|
return RawTypes.DEEP_SLEEP;
|
|
case ActivityKind.TYPE_LIGHT_SLEEP:
|
|
return RawTypes.LIGHT_SLEEP;
|
|
default:
|
|
return RawTypes.NOT_MEASURED;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public float normalizeIntensity(int rawIntensity) {
|
|
return rawIntensity;
|
|
}
|
|
|
|
@Override
|
|
public AbstractDao<HuaweiActivitySample, ?> getSampleDao() {
|
|
return getSession().getHuaweiActivitySampleDao();
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
protected Property getRawKindSampleProperty() {
|
|
return HuaweiActivitySampleDao.Properties.RawKind;
|
|
}
|
|
|
|
@NonNull
|
|
@Override
|
|
protected Property getTimestampSampleProperty() {
|
|
return HuaweiActivitySampleDao.Properties.Timestamp;
|
|
}
|
|
|
|
@NonNull
|
|
@Override
|
|
protected Property getDeviceIdentifierSampleProperty() {
|
|
return HuaweiActivitySampleDao.Properties.DeviceId;
|
|
}
|
|
|
|
@Override
|
|
public HuaweiActivitySample createActivitySample() {
|
|
return new HuaweiActivitySample();
|
|
}
|
|
|
|
private int getLastFetchTimestamp(QueryBuilder<HuaweiActivitySample> qb) {
|
|
Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
|
|
if (dbDevice == null)
|
|
return 0;
|
|
Property deviceProperty = HuaweiActivitySampleDao.Properties.DeviceId;
|
|
Property timestampProperty = HuaweiActivitySampleDao.Properties.Timestamp;
|
|
|
|
qb.where(deviceProperty.eq(dbDevice.getId()))
|
|
.orderDesc(timestampProperty)
|
|
.limit(1);
|
|
|
|
List<HuaweiActivitySample> samples = qb.build().list();
|
|
if (samples.isEmpty())
|
|
return 0;
|
|
|
|
HuaweiActivitySample sample = samples.get(0);
|
|
return sample.getTimestamp();
|
|
}
|
|
|
|
/**
|
|
* Gets last timestamp where the sleep data has been fully synchronized
|
|
* @return Last fully synchronized timestamp for sleep data
|
|
*/
|
|
public int getLastSleepFetchTimestamp() {
|
|
QueryBuilder<HuaweiActivitySample> qb = getSampleDao().queryBuilder();
|
|
Property sourceProperty = HuaweiActivitySampleDao.Properties.Source;
|
|
Property activityTypeProperty = HuaweiActivitySampleDao.Properties.RawKind;
|
|
|
|
qb.where(sourceProperty.eq(0x0d), activityTypeProperty.eq(0x01));
|
|
|
|
return getLastFetchTimestamp(qb);
|
|
}
|
|
|
|
/**
|
|
* Gets last timestamp where the step data has been fully synchronized
|
|
* @return Last fully synchronized timestamp for step data
|
|
*/
|
|
public int getLastStepFetchTimestamp() {
|
|
QueryBuilder<HuaweiActivitySample> qb = getSampleDao().queryBuilder();
|
|
Property sourceProperty = HuaweiActivitySampleDao.Properties.Source;
|
|
|
|
qb.where(sourceProperty.eq(0x0b));
|
|
|
|
return getLastFetchTimestamp(qb);
|
|
}
|
|
|
|
/**
|
|
* Makes a copy of a sample
|
|
* @param sample The sample to copy
|
|
* @return The copy of the sample
|
|
*/
|
|
private HuaweiActivitySample copySample(HuaweiActivitySample sample) {
|
|
HuaweiActivitySample sampleCopy = new HuaweiActivitySample(
|
|
sample.getTimestamp(),
|
|
sample.getDeviceId(),
|
|
sample.getUserId(),
|
|
sample.getOtherTimestamp(),
|
|
sample.getSource(),
|
|
sample.getRawKind(),
|
|
sample.getRawIntensity(),
|
|
sample.getSteps(),
|
|
sample.getCalories(),
|
|
sample.getDistance(),
|
|
sample.getSpo(),
|
|
sample.getHeartRate()
|
|
);
|
|
sampleCopy.setProvider(sample.getProvider());
|
|
return sampleCopy;
|
|
}
|
|
|
|
@Override
|
|
public void addGBActivitySample(HuaweiActivitySample activitySample) {
|
|
HuaweiActivitySample start = copySample(activitySample);
|
|
HuaweiActivitySample end = copySample(activitySample);
|
|
end.setTimestamp(start.getOtherTimestamp());
|
|
end.setSteps(ActivitySample.NOT_MEASURED);
|
|
end.setCalories(ActivitySample.NOT_MEASURED);
|
|
end.setDistance(ActivitySample.NOT_MEASURED);
|
|
end.setSpo(ActivitySample.NOT_MEASURED);
|
|
end.setHeartRate(ActivitySample.NOT_MEASURED);
|
|
end.setOtherTimestamp(start.getTimestamp());
|
|
|
|
getSampleDao().insertOrReplace(start);
|
|
getSampleDao().insertOrReplace(end);
|
|
}
|
|
|
|
@Override
|
|
public void addGBActivitySamples(HuaweiActivitySample[] activitySamples) {
|
|
List<HuaweiActivitySample> newSamples = new ArrayList<>();
|
|
for (HuaweiActivitySample sample : activitySamples) {
|
|
HuaweiActivitySample start = copySample(sample);
|
|
HuaweiActivitySample end = copySample(sample);
|
|
end.setTimestamp(start.getOtherTimestamp());
|
|
end.setSteps(ActivitySample.NOT_MEASURED);
|
|
end.setCalories(ActivitySample.NOT_MEASURED);
|
|
end.setDistance(ActivitySample.NOT_MEASURED);
|
|
end.setSpo(ActivitySample.NOT_MEASURED);
|
|
end.setHeartRate(ActivitySample.NOT_MEASURED);
|
|
end.setOtherTimestamp(start.getTimestamp());
|
|
|
|
newSamples.add(start);
|
|
newSamples.add(end);
|
|
}
|
|
getSampleDao().insertOrReplaceInTx(newSamples);
|
|
}
|
|
|
|
/**
|
|
* Gets the activity samples, ordered by timestamp
|
|
* @param timestampFrom Start timestamp
|
|
* @param timestampTo End timestamp
|
|
* @return List of activities between the timestamps, ordered by timestamp
|
|
*/
|
|
private List<HuaweiActivitySample> getRawOrderedActivitySamples(int timestampFrom, int timestampTo) {
|
|
QueryBuilder<HuaweiActivitySample> qb = getSampleDao().queryBuilder();
|
|
Property timestampProperty = getTimestampSampleProperty();
|
|
Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
|
|
if (dbDevice == null) {
|
|
// no device, no samples
|
|
return Collections.emptyList();
|
|
}
|
|
Property deviceProperty = getDeviceIdentifierSampleProperty();
|
|
qb.where(deviceProperty.eq(dbDevice.getId()), timestampProperty.ge(timestampFrom))
|
|
.where(timestampProperty.le(timestampTo))
|
|
.orderAsc(timestampProperty);
|
|
List<HuaweiActivitySample> samples = qb.build().list();
|
|
for (HuaweiActivitySample sample : samples) {
|
|
sample.setProvider(this);
|
|
}
|
|
detachFromSession();
|
|
return samples;
|
|
}
|
|
|
|
private List<HuaweiWorkoutDataSample> getRawOrderedWorkoutSamplesWithHeartRate(int timestampFrom, int timestampTo) {
|
|
Device dbDevice = DBHelper.findDevice(getDevice(), getSession());
|
|
if (dbDevice == null)
|
|
return Collections.emptyList();
|
|
|
|
QueryBuilder<HuaweiWorkoutDataSample> qb = getSession().getHuaweiWorkoutDataSampleDao().queryBuilder();
|
|
Property timestampProperty = HuaweiWorkoutDataSampleDao.Properties.Timestamp;
|
|
Property heartRateProperty = HuaweiWorkoutDataSampleDao.Properties.HeartRate;
|
|
Property deviceProperty = HuaweiWorkoutSummarySampleDao.Properties.DeviceId;
|
|
qb.join(HuaweiWorkoutDataSampleDao.Properties.WorkoutId, HuaweiWorkoutSummarySample.class, HuaweiWorkoutSummarySampleDao.Properties.WorkoutId)
|
|
.where(deviceProperty.eq(dbDevice.getId()));
|
|
qb.where(
|
|
timestampProperty.ge(timestampFrom),
|
|
timestampProperty.le(timestampTo),
|
|
heartRateProperty.notEq(ActivitySample.NOT_MEASURED)
|
|
).orderAsc(timestampProperty);
|
|
List<HuaweiWorkoutDataSample> samples = qb.build().list();
|
|
getSession().getHuaweiWorkoutSummarySampleDao().detachAll();
|
|
return samples;
|
|
}
|
|
|
|
private static class SampleLoopState {
|
|
public long deviceId = 0;
|
|
public long userId = 0;
|
|
|
|
int[] activityTypes = {};
|
|
|
|
public int sleepModifier = 0;
|
|
}
|
|
|
|
/*
|
|
* Note that this does a lot more than the normal implementation, as it takes care of everything
|
|
* that is necessary for proper displaying of data.
|
|
*
|
|
* This essentially boils down to four things:
|
|
* - It adds in the workout heart rate data
|
|
* - It adds a sample with intensity zero before start markers (start of block)
|
|
* - It adds a sample with intensity zero after end markers (end of block)
|
|
* - It modifies some blocks so the sleep data gets handled correctly
|
|
* The second and fourth are necessary for proper stats calculation, the third is mostly for
|
|
* nicer graphs.
|
|
*
|
|
* Note that the data in the database isn't changed, as the samples are detached.
|
|
*/
|
|
@Override
|
|
protected List<HuaweiActivitySample> getGBActivitySamples(int timestamp_from, int timestamp_to, int activityType) {
|
|
// Note that the result of this function has to be sorted by timestamp!
|
|
|
|
List<HuaweiActivitySample> rawSamples = getRawOrderedActivitySamples(timestamp_from, timestamp_to);
|
|
List<HuaweiWorkoutDataSample> workoutSamples = getRawOrderedWorkoutSamplesWithHeartRate(timestamp_from, timestamp_to);
|
|
|
|
List<HuaweiActivitySample> processedSamples = new ArrayList<>();
|
|
|
|
Iterator<HuaweiActivitySample> itRawSamples = rawSamples.iterator();
|
|
Iterator<HuaweiWorkoutDataSample> itWorkoutSamples = workoutSamples.iterator();
|
|
|
|
HuaweiActivitySample nextRawSample = null;
|
|
if (itRawSamples.hasNext())
|
|
nextRawSample = itRawSamples.next();
|
|
HuaweiWorkoutDataSample nextWorkoutSample = null;
|
|
if (itWorkoutSamples.hasNext())
|
|
nextWorkoutSample = itWorkoutSamples.next();
|
|
|
|
SampleLoopState state = new SampleLoopState();
|
|
if (nextRawSample != null) {
|
|
state.deviceId = nextRawSample.getDeviceId();
|
|
state.userId = nextRawSample.getUserId();
|
|
}
|
|
state.activityTypes = ActivityKind.mapToDBActivityTypes(activityType, this);
|
|
|
|
while (nextRawSample != null || nextWorkoutSample != null) {
|
|
if (nextRawSample == null) {
|
|
processWorkoutSample(processedSamples, state, nextWorkoutSample);
|
|
|
|
nextWorkoutSample = null;
|
|
if (itWorkoutSamples.hasNext())
|
|
nextWorkoutSample = itWorkoutSamples.next();
|
|
} else if (nextWorkoutSample == null) {
|
|
processRawSample(processedSamples, state, nextRawSample);
|
|
|
|
nextRawSample = null;
|
|
if (itRawSamples.hasNext())
|
|
nextRawSample = itRawSamples.next();
|
|
} else if (nextRawSample.getTimestamp() > nextWorkoutSample.getTimestamp()) {
|
|
processWorkoutSample(processedSamples, state, nextWorkoutSample);
|
|
|
|
nextWorkoutSample = null;
|
|
if (itWorkoutSamples.hasNext())
|
|
nextWorkoutSample = itWorkoutSamples.next();
|
|
} else {
|
|
processRawSample(processedSamples, state, nextRawSample);
|
|
|
|
nextRawSample = null;
|
|
if (itRawSamples.hasNext())
|
|
nextRawSample = itRawSamples.next();
|
|
}
|
|
}
|
|
|
|
processedSamples = interpolate(processedSamples);
|
|
|
|
return processedSamples;
|
|
}
|
|
|
|
private List<HuaweiActivitySample> interpolate(List<HuaweiActivitySample> processedSamples) {
|
|
List<HuaweiActivitySample> retv = new ArrayList<>();
|
|
|
|
if (processedSamples.size() == 0)
|
|
return retv;
|
|
|
|
HuaweiActivitySample lastSample = processedSamples.get(0);
|
|
retv.add(lastSample);
|
|
for (int i = 1; i < processedSamples.size() - 1; i++) {
|
|
HuaweiActivitySample sample = processedSamples.get(i);
|
|
|
|
int timediff = sample.getTimestamp() - lastSample.getTimestamp();
|
|
if (timediff > 60) {
|
|
if (lastSample.getRawKind() != -1 && sample.getRawKind() != lastSample.getRawKind()) {
|
|
HuaweiActivitySample postSample = new HuaweiActivitySample(
|
|
lastSample.getTimestamp() + 1,
|
|
lastSample.getDeviceId(),
|
|
lastSample.getUserId(),
|
|
0,
|
|
(byte) 0x00,
|
|
ActivitySample.NOT_MEASURED,
|
|
0,
|
|
ActivitySample.NOT_MEASURED,
|
|
ActivitySample.NOT_MEASURED,
|
|
ActivitySample.NOT_MEASURED,
|
|
ActivitySample.NOT_MEASURED,
|
|
ActivitySample.NOT_MEASURED
|
|
);
|
|
postSample.setProvider(this);
|
|
retv.add(postSample);
|
|
}
|
|
|
|
if (sample.getRawKind() != -1 && sample.getRawKind() != lastSample.getRawKind()) {
|
|
HuaweiActivitySample preSample = new HuaweiActivitySample(
|
|
sample.getTimestamp() - 1,
|
|
sample.getDeviceId(),
|
|
sample.getUserId(),
|
|
0,
|
|
(byte) 0x00,
|
|
ActivitySample.NOT_MEASURED,
|
|
0,
|
|
ActivitySample.NOT_MEASURED,
|
|
ActivitySample.NOT_MEASURED,
|
|
ActivitySample.NOT_MEASURED,
|
|
ActivitySample.NOT_MEASURED,
|
|
ActivitySample.NOT_MEASURED
|
|
);
|
|
preSample.setProvider(this);
|
|
retv.add(preSample);
|
|
}
|
|
}
|
|
|
|
retv.add(sample);
|
|
lastSample = sample;
|
|
}
|
|
|
|
if (lastSample.getRawKind() != -1) {
|
|
HuaweiActivitySample postSample = new HuaweiActivitySample(
|
|
lastSample.getTimestamp() + 1,
|
|
lastSample.getDeviceId(),
|
|
lastSample.getUserId(),
|
|
0,
|
|
(byte) 0x00,
|
|
ActivitySample.NOT_MEASURED,
|
|
0,
|
|
ActivitySample.NOT_MEASURED,
|
|
ActivitySample.NOT_MEASURED,
|
|
ActivitySample.NOT_MEASURED,
|
|
ActivitySample.NOT_MEASURED,
|
|
ActivitySample.NOT_MEASURED
|
|
);
|
|
postSample.setProvider(this);
|
|
retv.add(postSample);
|
|
}
|
|
|
|
return retv;
|
|
}
|
|
|
|
private void processRawSample(List<HuaweiActivitySample> processedSamples, SampleLoopState state, HuaweiActivitySample sample) {
|
|
// Filter on Source 0x0d, Type 0x01, until we know what it is and how we should handle them.
|
|
// Just showing them currently has some issues.
|
|
if (sample.getSource() == FitnessData.MessageData.sleepId && sample.getRawKind() == RawTypes.UNKNOWN)
|
|
return;
|
|
|
|
HuaweiActivitySample lastSample = null;
|
|
|
|
boolean isStartMarker = sample.getTimestamp() < sample.getOtherTimestamp();
|
|
|
|
// Handle preferences for wakeup status ignore - can fix some quirks on some devices
|
|
if (sample.getRawKind() == 0x08) {
|
|
SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
|
|
if (isStartMarker && prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_IGNORE_WAKEUP_STATUS_START, false))
|
|
return;
|
|
if (!isStartMarker && prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_IGNORE_WAKEUP_STATUS_END, false))
|
|
return;
|
|
}
|
|
|
|
// Backdate the end marker by one - otherwise the interpolation fails
|
|
if (sample.getTimestamp() > sample.getOtherTimestamp())
|
|
sample.setTimestamp(sample.getTimestamp() - 1);
|
|
|
|
if (processedSamples.size() > 0)
|
|
lastSample = processedSamples.get(processedSamples.size() - 1);
|
|
if (lastSample != null && lastSample.getTimestamp() == sample.getTimestamp()) {
|
|
// Merge the samples - only if there isn't any data yet, except the kind
|
|
|
|
if (lastSample.getRawKind() == -1)
|
|
lastSample.setRawKind(sample.getRawKind());
|
|
// Do overwrite the kind if the new sample is a starting sample
|
|
if (isStartMarker && sample.getRawKind() != -1) {
|
|
lastSample.setRawKind(sample.getRawKind());
|
|
lastSample.setOtherTimestamp(sample.getOtherTimestamp()); // Necessary for interpolation
|
|
}
|
|
|
|
if (lastSample.getRawIntensity() == -1)
|
|
lastSample.setRawIntensity(sample.getRawIntensity());
|
|
if (lastSample.getSteps() == -1)
|
|
lastSample.setSteps(sample.getSteps());
|
|
if (lastSample.getCalories() == -1)
|
|
lastSample.setCalories(sample.getCalories());
|
|
if (lastSample.getDistance() == -1)
|
|
lastSample.setDistance(sample.getDistance());
|
|
if (lastSample.getSpo() == -1)
|
|
lastSample.setSpo(sample.getSpo());
|
|
if (lastSample.getHeartRate() == -1)
|
|
lastSample.setHeartRate(sample.getHeartRate());
|
|
if (lastSample.getSource() != sample.getSource())
|
|
lastSample.setSource((byte) 0x00);
|
|
} else {
|
|
if (state.sleepModifier != 0)
|
|
sample.setRawKind(state.sleepModifier);
|
|
processedSamples.add(sample);
|
|
}
|
|
|
|
if (sample.getSource() == FitnessData.MessageData.sleepId && (sample.getRawKind() == RawTypes.LIGHT_SLEEP || sample.getRawKind() == RawTypes.DEEP_SLEEP)) {
|
|
if (isStartMarker)
|
|
state.sleepModifier = sample.getRawKind();
|
|
else
|
|
state.sleepModifier = 0;
|
|
}
|
|
}
|
|
|
|
private void processWorkoutSample(List<HuaweiActivitySample> processedSamples, SampleLoopState state, HuaweiWorkoutDataSample workoutSample) {
|
|
processRawSample(processedSamples, state, convertWorkoutSampleToActivitySample(workoutSample, state));
|
|
}
|
|
|
|
private HuaweiActivitySample convertWorkoutSampleToActivitySample(HuaweiWorkoutDataSample workoutSample, SampleLoopState state) {
|
|
int hr = workoutSample.getHeartRate() & 0xFF;
|
|
HuaweiActivitySample newSample = new HuaweiActivitySample(
|
|
workoutSample.getTimestamp(),
|
|
state.deviceId,
|
|
state.userId,
|
|
0,
|
|
(byte) 0x00,
|
|
ActivitySample.NOT_MEASURED,
|
|
ActivitySample.NOT_MEASURED,
|
|
ActivitySample.NOT_MEASURED,
|
|
ActivitySample.NOT_MEASURED,
|
|
ActivitySample.NOT_MEASURED,
|
|
ActivitySample.NOT_MEASURED,
|
|
hr
|
|
);
|
|
newSample.setProvider(this);
|
|
return newSample;
|
|
}
|
|
}
|