1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2025-01-25 00:57:33 +01:00

Add support for REM sleep

This commit is contained in:
José Rebelo 2022-08-24 11:21:59 +01:00 committed by Gitea
parent 28a26710d9
commit a919286496
15 changed files with 173 additions and 34 deletions

View File

@ -116,6 +116,11 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
return coordinator != null && coordinator.supportsHeartRateMeasurement(device); return coordinator != null && coordinator.supportsHeartRateMeasurement(device);
} }
public boolean supportsRemSleep(GBDevice device) {
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
return coordinator != null && coordinator.supportsRemSleep();
}
protected static final class ActivityConfig { protected static final class ActivityConfig {
public final int type; public final int type;
public final String label; public final String label;
@ -131,6 +136,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
protected ActivityConfig akActivity; protected ActivityConfig akActivity;
protected ActivityConfig akLightSleep; protected ActivityConfig akLightSleep;
protected ActivityConfig akDeepSleep; protected ActivityConfig akDeepSleep;
protected ActivityConfig akRemSleep;
protected ActivityConfig akNotWorn; protected ActivityConfig akNotWorn;
@ -142,6 +148,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
protected int HEARTRATE_FILL_COLOR; protected int HEARTRATE_FILL_COLOR;
protected int AK_ACTIVITY_COLOR; protected int AK_ACTIVITY_COLOR;
protected int AK_DEEP_SLEEP_COLOR; protected int AK_DEEP_SLEEP_COLOR;
protected int AK_REM_SLEEP_COLOR;
protected int AK_LIGHT_SLEEP_COLOR; protected int AK_LIGHT_SLEEP_COLOR;
protected int AK_NOT_WORN_COLOR; protected int AK_NOT_WORN_COLOR;
@ -194,6 +201,8 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
AK_DEEP_SLEEP_COLOR = runningColor.data; AK_DEEP_SLEEP_COLOR = runningColor.data;
getContext().getTheme().resolveAttribute(R.attr.chart_light_sleep, runningColor, true); getContext().getTheme().resolveAttribute(R.attr.chart_light_sleep, runningColor, true);
AK_LIGHT_SLEEP_COLOR = runningColor.data; AK_LIGHT_SLEEP_COLOR = runningColor.data;
getContext().getTheme().resolveAttribute(R.attr.chart_rem_sleep, runningColor, true);
AK_REM_SLEEP_COLOR = runningColor.data;
getContext().getTheme().resolveAttribute(R.attr.chart_not_worn, runningColor, true); getContext().getTheme().resolveAttribute(R.attr.chart_not_worn, runningColor, true);
AK_NOT_WORN_COLOR = runningColor.data; AK_NOT_WORN_COLOR = runningColor.data;
@ -203,6 +212,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
akActivity = new ActivityConfig(ActivityKind.TYPE_ACTIVITY, getString(R.string.abstract_chart_fragment_kind_activity), AK_ACTIVITY_COLOR); akActivity = new ActivityConfig(ActivityKind.TYPE_ACTIVITY, getString(R.string.abstract_chart_fragment_kind_activity), AK_ACTIVITY_COLOR);
akLightSleep = new ActivityConfig(ActivityKind.TYPE_LIGHT_SLEEP, getString(R.string.abstract_chart_fragment_kind_light_sleep), AK_LIGHT_SLEEP_COLOR); akLightSleep = new ActivityConfig(ActivityKind.TYPE_LIGHT_SLEEP, getString(R.string.abstract_chart_fragment_kind_light_sleep), AK_LIGHT_SLEEP_COLOR);
akDeepSleep = new ActivityConfig(ActivityKind.TYPE_DEEP_SLEEP, getString(R.string.abstract_chart_fragment_kind_deep_sleep), AK_DEEP_SLEEP_COLOR); akDeepSleep = new ActivityConfig(ActivityKind.TYPE_DEEP_SLEEP, getString(R.string.abstract_chart_fragment_kind_deep_sleep), AK_DEEP_SLEEP_COLOR);
akRemSleep = new ActivityConfig(ActivityKind.TYPE_REM_SLEEP, getString(R.string.abstract_chart_fragment_kind_rem_sleep), AK_REM_SLEEP_COLOR);
akNotWorn = new ActivityConfig(ActivityKind.TYPE_NOT_WORN, getString(R.string.abstract_chart_fragment_kind_not_worn), AK_NOT_WORN_COLOR); akNotWorn = new ActivityConfig(ActivityKind.TYPE_NOT_WORN, getString(R.string.abstract_chart_fragment_kind_not_worn), AK_NOT_WORN_COLOR);
} }
@ -331,6 +341,8 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
return akDeepSleep.color; return akDeepSleep.color;
case ActivityKind.TYPE_LIGHT_SLEEP: case ActivityKind.TYPE_LIGHT_SLEEP:
return akLightSleep.color; return akLightSleep.color;
case ActivityKind.TYPE_REM_SLEEP:
return akRemSleep.color;
case ActivityKind.TYPE_ACTIVITY: case ActivityKind.TYPE_ACTIVITY:
return akActivity.color; return akActivity.color;
} }
@ -456,6 +468,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
List<Entry> activityEntries = new ArrayList<>(numEntries); List<Entry> activityEntries = new ArrayList<>(numEntries);
List<Entry> deepSleepEntries = new ArrayList<>(numEntries); List<Entry> deepSleepEntries = new ArrayList<>(numEntries);
List<Entry> lightSleepEntries = new ArrayList<>(numEntries); List<Entry> lightSleepEntries = new ArrayList<>(numEntries);
List<Entry> remSleepEntries = new ArrayList<>(numEntries);
List<Entry> notWornEntries = new ArrayList<>(numEntries); List<Entry> notWornEntries = new ArrayList<>(numEntries);
boolean hr = supportsHeartrate(gbDevice); boolean hr = supportsHeartrate(gbDevice);
List<Entry> heartrateEntries = hr ? new ArrayList<Entry>(numEntries) : null; List<Entry> heartrateEntries = hr ? new ArrayList<Entry>(numEntries) : null;
@ -490,6 +503,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
deepSleepEntries.add(createLineEntry(0, ts - 1)); deepSleepEntries.add(createLineEntry(0, ts - 1));
lightSleepEntries.add(createLineEntry(0, ts)); lightSleepEntries.add(createLineEntry(0, ts));
remSleepEntries.add(createLineEntry(0, ts));
notWornEntries.add(createLineEntry(0, ts)); notWornEntries.add(createLineEntry(0, ts));
activityEntries.add(createLineEntry(0, ts)); activityEntries.add(createLineEntry(0, ts));
} }
@ -500,17 +514,30 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
lightSleepEntries.add(createLineEntry(0, ts - 1)); lightSleepEntries.add(createLineEntry(0, ts - 1));
deepSleepEntries.add(createLineEntry(0, ts)); deepSleepEntries.add(createLineEntry(0, ts));
remSleepEntries.add(createLineEntry(0, ts));
notWornEntries.add(createLineEntry(0, ts)); notWornEntries.add(createLineEntry(0, ts));
activityEntries.add(createLineEntry(0, ts)); activityEntries.add(createLineEntry(0, ts));
} }
lightSleepEntries.add(createLineEntry(value, ts)); lightSleepEntries.add(createLineEntry(value, ts));
break; break;
case ActivityKind.TYPE_REM_SLEEP:
if (last_type != type) {
remSleepEntries.add(createLineEntry(0, ts - 1));
lightSleepEntries.add(createLineEntry(0, ts));
deepSleepEntries.add(createLineEntry(0, ts));
notWornEntries.add(createLineEntry(0, ts));
activityEntries.add(createLineEntry(0, ts));
}
remSleepEntries.add(createLineEntry(value, ts));
break;
case ActivityKind.TYPE_NOT_WORN: case ActivityKind.TYPE_NOT_WORN:
if (last_type != type) { if (last_type != type) {
notWornEntries.add(createLineEntry(0, ts - 1)); notWornEntries.add(createLineEntry(0, ts - 1));
lightSleepEntries.add(createLineEntry(0, ts)); lightSleepEntries.add(createLineEntry(0, ts));
deepSleepEntries.add(createLineEntry(0, ts)); deepSleepEntries.add(createLineEntry(0, ts));
remSleepEntries.add(createLineEntry(0, ts));
activityEntries.add(createLineEntry(0, ts)); activityEntries.add(createLineEntry(0, ts));
} }
notWornEntries.add(createLineEntry(SleepUtils.Y_VALUE_DEEP_SLEEP, ts)); //a small value, just to show something on the graphs notWornEntries.add(createLineEntry(SleepUtils.Y_VALUE_DEEP_SLEEP, ts)); //a small value, just to show something on the graphs
@ -528,6 +555,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
lightSleepEntries.add(createLineEntry(0, ts)); lightSleepEntries.add(createLineEntry(0, ts));
notWornEntries.add(createLineEntry(0, ts)); notWornEntries.add(createLineEntry(0, ts));
deepSleepEntries.add(createLineEntry(0, ts)); deepSleepEntries.add(createLineEntry(0, ts));
remSleepEntries.add(createLineEntry(0, ts));
} }
activityEntries.add(createLineEntry(value, ts)); activityEntries.add(createLineEntry(value, ts));
} }
@ -576,6 +604,10 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
lineDataSets.add(deepSleepSet); lineDataSets.add(deepSleepSet);
LineDataSet lightSleepSet = createDataSet(lightSleepEntries, akLightSleep.color, "Light Sleep"); LineDataSet lightSleepSet = createDataSet(lightSleepEntries, akLightSleep.color, "Light Sleep");
lineDataSets.add(lightSleepSet); lineDataSets.add(lightSleepSet);
if (supportsRemSleep(gbDevice)) {
LineDataSet remSleepSet = createDataSet(remSleepEntries, akRemSleep.color, "REM Sleep");
lineDataSets.add(remSleepSet);
}
LineDataSet notWornSet = createDataSet(notWornEntries, akNotWorn.color, "Not worn"); LineDataSet notWornSet = createDataSet(notWornEntries, akNotWorn.color, "Not worn");
lineDataSets.add(notWornSet); lineDataSets.add(notWornSet);

View File

@ -39,6 +39,7 @@ public class ActivityAnalysis {
public ActivityAmounts calculateActivityAmounts(List<? extends ActivitySample> samples) { public ActivityAmounts calculateActivityAmounts(List<? extends ActivitySample> samples) {
ActivityAmount deepSleep = new ActivityAmount(ActivityKind.TYPE_DEEP_SLEEP); ActivityAmount deepSleep = new ActivityAmount(ActivityKind.TYPE_DEEP_SLEEP);
ActivityAmount lightSleep = new ActivityAmount(ActivityKind.TYPE_LIGHT_SLEEP); ActivityAmount lightSleep = new ActivityAmount(ActivityKind.TYPE_LIGHT_SLEEP);
ActivityAmount remSleep = new ActivityAmount(ActivityKind.TYPE_REM_SLEEP);
ActivityAmount notWorn = new ActivityAmount(ActivityKind.TYPE_NOT_WORN); ActivityAmount notWorn = new ActivityAmount(ActivityKind.TYPE_NOT_WORN);
ActivityAmount activity = new ActivityAmount(ActivityKind.TYPE_ACTIVITY); ActivityAmount activity = new ActivityAmount(ActivityKind.TYPE_ACTIVITY);
@ -53,6 +54,9 @@ public class ActivityAnalysis {
case ActivityKind.TYPE_LIGHT_SLEEP: case ActivityKind.TYPE_LIGHT_SLEEP:
amount = lightSleep; amount = lightSleep;
break; break;
case ActivityKind.TYPE_REM_SLEEP:
amount = remSleep;
break;
case ActivityKind.TYPE_NOT_WORN: case ActivityKind.TYPE_NOT_WORN:
amount = notWorn; amount = notWorn;
break; break;
@ -108,6 +112,9 @@ public class ActivityAnalysis {
if (lightSleep.getTotalSeconds() > 0) { if (lightSleep.getTotalSeconds() > 0) {
result.addAmount(lightSleep); result.addAmount(lightSleep);
} }
if (remSleep.getTotalSeconds() > 0) {
result.addAmount(remSleep);
}
if (activity.getTotalSeconds() > 0) { if (activity.getTotalSeconds() > 0) {
result.addAmount(activity); result.addAmount(activity);
} }

View File

@ -166,6 +166,13 @@ public class ActivitySleepChartFragment extends AbstractChartFragment {
deepSleepEntry.formColor = akDeepSleep.color; deepSleepEntry.formColor = akDeepSleep.color;
legendEntries.add(deepSleepEntry); legendEntries.add(deepSleepEntry);
if (supportsRemSleep(getChartsHost().getDevice())) {
LegendEntry remSleepEntry = new LegendEntry();
remSleepEntry.label = akRemSleep.label;
remSleepEntry.formColor = akRemSleep.color;
legendEntries.add(remSleepEntry);
}
LegendEntry notWornEntry = new LegendEntry(); LegendEntry notWornEntry = new LegendEntry();
notWornEntry.label = akNotWorn.label; notWornEntry.label = akNotWorn.label;
notWornEntry.formColor = akNotWorn.color; notWornEntry.formColor = akNotWorn.color;

View File

@ -36,6 +36,7 @@ public class SleepAnalysis {
Date sleepEnd = null; Date sleepEnd = null;
long lightSleepDuration = 0; long lightSleepDuration = 0;
long deepSleepDuration = 0; long deepSleepDuration = 0;
long remSleepDuration = 0;
long durationSinceLastSleep = 0; long durationSinceLastSleep = 0;
for (ActivitySample sample : samples) { for (ActivitySample sample : samples) {
@ -47,12 +48,13 @@ public class SleepAnalysis {
durationSinceLastSleep = 0; durationSinceLastSleep = 0;
} else { } else {
//exclude "not worn" times from sleep sessions as this makes a discrepancy with the charts //exclude "not worn" times from sleep sessions as this makes a discrepancy with the charts
if (lightSleepDuration + deepSleepDuration > MIN_SESSION_LENGTH) if (lightSleepDuration + deepSleepDuration + remSleepDuration > MIN_SESSION_LENGTH)
result.add(new SleepSession(sleepStart, sleepEnd, lightSleepDuration, deepSleepDuration)); result.add(new SleepSession(sleepStart, sleepEnd, lightSleepDuration, deepSleepDuration, remSleepDuration));
sleepStart = null; sleepStart = null;
sleepEnd = null; sleepEnd = null;
lightSleepDuration = 0; lightSleepDuration = 0;
deepSleepDuration = 0; deepSleepDuration = 0;
remSleepDuration = 0;
} }
if (previousSample != null) { if (previousSample != null) {
@ -61,29 +63,34 @@ public class SleepAnalysis {
lightSleepDuration += durationSinceLastSample; lightSleepDuration += durationSinceLastSample;
} else if (sample.getKind() == ActivityKind.TYPE_DEEP_SLEEP) { } else if (sample.getKind() == ActivityKind.TYPE_DEEP_SLEEP) {
deepSleepDuration += durationSinceLastSample; deepSleepDuration += durationSinceLastSample;
} else if (sample.getKind() == ActivityKind.TYPE_REM_SLEEP) {
remSleepDuration += durationSinceLastSample;
} else { } else {
durationSinceLastSleep += durationSinceLastSample; durationSinceLastSleep += durationSinceLastSample;
if (sleepStart != null && durationSinceLastSleep > MAX_WAKE_PHASE_LENGTH) { if (sleepStart != null && durationSinceLastSleep > MAX_WAKE_PHASE_LENGTH) {
if (lightSleepDuration + deepSleepDuration > MIN_SESSION_LENGTH) if (lightSleepDuration + deepSleepDuration + remSleepDuration > MIN_SESSION_LENGTH)
result.add(new SleepSession(sleepStart, sleepEnd, lightSleepDuration, deepSleepDuration)); result.add(new SleepSession(sleepStart, sleepEnd, lightSleepDuration, deepSleepDuration, remSleepDuration));
sleepStart = null; sleepStart = null;
sleepEnd = null; sleepEnd = null;
lightSleepDuration = 0; lightSleepDuration = 0;
deepSleepDuration = 0; deepSleepDuration = 0;
remSleepDuration = 0;
} }
} }
} }
previousSample = sample; previousSample = sample;
} }
if (lightSleepDuration + deepSleepDuration > MIN_SESSION_LENGTH) { if (lightSleepDuration + deepSleepDuration + remSleepDuration > MIN_SESSION_LENGTH) {
result.add(new SleepSession(sleepStart, sleepEnd, lightSleepDuration, deepSleepDuration)); result.add(new SleepSession(sleepStart, sleepEnd, lightSleepDuration, deepSleepDuration, remSleepDuration));
} }
return result; return result;
} }
private boolean isSleep(ActivitySample sample) { private boolean isSleep(ActivitySample sample) {
return sample.getKind() == ActivityKind.TYPE_DEEP_SLEEP || sample.getKind() == ActivityKind.TYPE_LIGHT_SLEEP; return sample.getKind() == ActivityKind.TYPE_DEEP_SLEEP ||
sample.getKind() == ActivityKind.TYPE_LIGHT_SLEEP ||
sample.getKind() == ActivityKind.TYPE_REM_SLEEP;
} }
private Date getDateFromSample(ActivitySample sample) { private Date getDateFromSample(ActivitySample sample) {
@ -96,15 +103,18 @@ public class SleepAnalysis {
private final Date sleepEnd; private final Date sleepEnd;
private final long lightSleepDuration; private final long lightSleepDuration;
private final long deepSleepDuration; private final long deepSleepDuration;
private final long remSleepDuration;
private SleepSession(Date sleepStart, private SleepSession(Date sleepStart,
Date sleepEnd, Date sleepEnd,
long lightSleepDuration, long lightSleepDuration,
long deepSleepDuration) { long deepSleepDuration,
long remSleepDuration) {
this.sleepStart = sleepStart; this.sleepStart = sleepStart;
this.sleepEnd = sleepEnd; this.sleepEnd = sleepEnd;
this.lightSleepDuration = lightSleepDuration; this.lightSleepDuration = lightSleepDuration;
this.deepSleepDuration = deepSleepDuration; this.deepSleepDuration = deepSleepDuration;
this.remSleepDuration = remSleepDuration;
} }
public Date getSleepStart() { public Date getSleepStart() {
@ -122,5 +132,9 @@ public class SleepAnalysis {
public long getDeepSleepDuration() { public long getDeepSleepDuration() {
return deepSleepDuration; return deepSleepDuration;
} }
public long getRemSleepDuration() {
return remSleepDuration;
}
} }
} }

View File

@ -134,27 +134,25 @@ public class SleepChartFragment extends AbstractChartFragment {
final long lightSleepDuration = calculateLightSleepDuration(sleepSessions); final long lightSleepDuration = calculateLightSleepDuration(sleepSessions);
final long deepSleepDuration = calculateDeepSleepDuration(sleepSessions); final long deepSleepDuration = calculateDeepSleepDuration(sleepSessions);
final long remSleepDuration = calculateRemSleepDuration(sleepSessions);
final long totalSeconds = lightSleepDuration + deepSleepDuration; final long totalSeconds = lightSleepDuration + deepSleepDuration + remSleepDuration;
final List<PieEntry> entries; final List<PieEntry> entries = new ArrayList<>();
final List<Integer> colors; final List<Integer> colors = new ArrayList<>();
if (sleepSessions.isEmpty()) { if (!sleepSessions.isEmpty()) {
entries = Collections.emptyList(); entries.add(new PieEntry(lightSleepDuration, getActivity().getString(R.string.abstract_chart_fragment_kind_light_sleep)));
colors = Collections.emptyList(); entries.add(new PieEntry(deepSleepDuration, getActivity().getString(R.string.abstract_chart_fragment_kind_deep_sleep)));
} else { colors.add(getColorFor(ActivityKind.TYPE_LIGHT_SLEEP));
entries = Arrays.asList( colors.add(getColorFor(ActivityKind.TYPE_DEEP_SLEEP));
new PieEntry(lightSleepDuration, getActivity().getString(R.string.abstract_chart_fragment_kind_light_sleep)),
new PieEntry(deepSleepDuration, getActivity().getString(R.string.abstract_chart_fragment_kind_deep_sleep)) if (supportsRemSleep(mGBDevice)) {
); entries.add(new PieEntry(remSleepDuration, getActivity().getString(R.string.abstract_chart_fragment_kind_rem_sleep)));
colors = Arrays.asList( colors.add(getColorFor(ActivityKind.TYPE_REM_SLEEP));
getColorFor(ActivityKind.TYPE_LIGHT_SLEEP), }
getColorFor(ActivityKind.TYPE_DEEP_SLEEP)
);
} }
String totalSleep = DateTimeUtils.formatDurationHoursMinutes(totalSeconds, TimeUnit.SECONDS); String totalSleep = DateTimeUtils.formatDurationHoursMinutes(totalSeconds, TimeUnit.SECONDS);
PieDataSet set = new PieDataSet(entries, ""); PieDataSet set = new PieDataSet(entries, "");
set.setValueFormatter(new ValueFormatter() { set.setValueFormatter(new ValueFormatter() {
@ -190,6 +188,14 @@ public class SleepChartFragment extends AbstractChartFragment {
return result; return result;
} }
private long calculateRemSleepDuration(List<SleepSession> sleepSessions) {
long result = 0;
for (SleepSession sleepSession : sleepSessions) {
result += sleepSession.getRemSleepDuration();
}
return result;
}
@Override @Override
protected void updateChartsnUIThread(ChartsData chartsData) { protected void updateChartsnUIThread(ChartsData chartsData) {
MyChartsData mcd = (MyChartsData) chartsData; MyChartsData mcd = (MyChartsData) chartsData;
@ -424,6 +430,14 @@ public class SleepChartFragment extends AbstractChartFragment {
deepSleepEntry.label = akDeepSleep.label; deepSleepEntry.label = akDeepSleep.label;
deepSleepEntry.formColor = akDeepSleep.color; deepSleepEntry.formColor = akDeepSleep.color;
legendEntries.add(deepSleepEntry); legendEntries.add(deepSleepEntry);
if (supportsRemSleep(getChartsHost().getDevice())) {
LegendEntry remSleepEntry = new LegendEntry();
remSleepEntry.label = akRemSleep.label;
remSleepEntry.formColor = akRemSleep.color;
legendEntries.add(remSleepEntry);
}
heartRateIcon.setVisibility(View.GONE); //hide heart icon heartRateIcon.setVisibility(View.GONE); //hide heart icon
intensityTotalIcon.setVisibility(View.GONE); //hide intensity icon intensityTotalIcon.setVisibility(View.GONE); //hide intensity icon

View File

@ -22,6 +22,8 @@ import com.github.mikephil.charting.components.Legend;
import com.github.mikephil.charting.components.LegendEntry; import com.github.mikephil.charting.components.LegendEntry;
import com.github.mikephil.charting.formatter.ValueFormatter; import com.github.mikephil.charting.formatter.ValueFormatter;
import org.apache.commons.lang3.ArrayUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -66,7 +68,9 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment {
long balance = 0; long balance = 0;
for (ActivityAmount amount : activityAmounts.getAmounts()) { for (ActivityAmount amount : activityAmounts.getAmounts()) {
if (amount.getActivityKind() == ActivityKind.TYPE_DEEP_SLEEP || amount.getActivityKind() == ActivityKind.TYPE_LIGHT_SLEEP) { if (amount.getActivityKind() == ActivityKind.TYPE_DEEP_SLEEP ||
amount.getActivityKind() == ActivityKind.TYPE_LIGHT_SLEEP ||
amount.getActivityKind() == ActivityKind.TYPE_REM_SLEEP) {
balance += amount.getTotalSeconds(); balance += amount.getTotalSeconds();
} }
} }
@ -89,16 +93,24 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment {
float[] getTotalsForActivityAmounts(ActivityAmounts activityAmounts) { float[] getTotalsForActivityAmounts(ActivityAmounts activityAmounts) {
long totalSecondsDeepSleep = 0; long totalSecondsDeepSleep = 0;
long totalSecondsLightSleep = 0; long totalSecondsLightSleep = 0;
long totalSecondsRemSleep = 0;
for (ActivityAmount amount : activityAmounts.getAmounts()) { for (ActivityAmount amount : activityAmounts.getAmounts()) {
if (amount.getActivityKind() == ActivityKind.TYPE_DEEP_SLEEP) { if (amount.getActivityKind() == ActivityKind.TYPE_DEEP_SLEEP) {
totalSecondsDeepSleep += amount.getTotalSeconds(); totalSecondsDeepSleep += amount.getTotalSeconds();
} else if (amount.getActivityKind() == ActivityKind.TYPE_LIGHT_SLEEP) { } else if (amount.getActivityKind() == ActivityKind.TYPE_LIGHT_SLEEP) {
totalSecondsLightSleep += amount.getTotalSeconds(); totalSecondsLightSleep += amount.getTotalSeconds();
} else if (amount.getActivityKind() == ActivityKind.TYPE_REM_SLEEP) {
totalSecondsRemSleep += amount.getTotalSeconds();
} }
} }
int totalMinutesDeepSleep = (int) (totalSecondsDeepSleep / 60); int totalMinutesDeepSleep = (int) (totalSecondsDeepSleep / 60);
int totalMinutesLightSleep = (int) (totalSecondsLightSleep / 60); int totalMinutesLightSleep = (int) (totalSecondsLightSleep / 60);
return new float[]{totalMinutesDeepSleep, totalMinutesLightSleep}; int totalMinutesRemSleep = (int) (totalSecondsRemSleep / 60);
if (supportsRemSleep(getChartsHost().getDevice())) {
return new float[]{totalMinutesDeepSleep, totalMinutesLightSleep, totalMinutesRemSleep};
} else {
return new float[]{totalMinutesDeepSleep, totalMinutesLightSleep};
}
} }
@Override @Override
@ -108,7 +120,14 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment {
@Override @Override
String[] getPieLabels() { String[] getPieLabels() {
return new String[]{getString(R.string.abstract_chart_fragment_kind_deep_sleep), getString(R.string.abstract_chart_fragment_kind_light_sleep)}; String[] labels = {
getString(R.string.abstract_chart_fragment_kind_deep_sleep),
getString(R.string.abstract_chart_fragment_kind_light_sleep)
};
if (supportsRemSleep(getChartsHost().getDevice())) {
labels = ArrayUtils.add(labels, getString(R.string.abstract_chart_fragment_kind_rem_sleep));
}
return labels;
} }
@Override @Override
@ -143,7 +162,11 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment {
@Override @Override
int[] getColors() { int[] getColors() {
return new int[]{akDeepSleep.color, akLightSleep.color}; int[] colors = {akDeepSleep.color, akLightSleep.color};
if (supportsRemSleep(getChartsHost().getDevice())) {
colors = ArrayUtils.add(colors, akRemSleep.color);
}
return colors;
} }
@Override @Override
@ -160,6 +183,13 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment {
deepSleepEntry.formColor = akDeepSleep.color; deepSleepEntry.formColor = akDeepSleep.color;
legendEntries.add(deepSleepEntry); legendEntries.add(deepSleepEntry);
if (supportsRemSleep(getChartsHost().getDevice())) {
LegendEntry remSleepEntry = new LegendEntry();
remSleepEntry.label = akRemSleep.label;
remSleepEntry.formColor = akRemSleep.color;
legendEntries.add(remSleepEntry);
}
chart.getLegend().setCustom(legendEntries); chart.getLegend().setCustom(legendEntries);
chart.getLegend().setTextColor(LEGEND_TEXT_COLOR); chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
chart.getLegend().setWordWrapEnabled(true); chart.getLegend().setWordWrapEnabled(true);

View File

@ -272,6 +272,11 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
return false; return false;
} }
@Override
public boolean supportsRemSleep() {
return false;
}
@Override @Override
public boolean supportsWeather() { public boolean supportsWeather() {
return false; return false;

View File

@ -33,6 +33,7 @@ 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.model.ActivityKind; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
/** /**
* Base class for all sample providers. A Sample provider is device specific and provides * Base class for all sample providers. A Sample provider is device specific and provides
@ -73,10 +74,18 @@ public abstract class AbstractSampleProvider<T extends AbstractActivitySample> i
@Override @Override
public List<T> getSleepSamples(int timestamp_from, int timestamp_to) { public List<T> getSleepSamples(int timestamp_from, int timestamp_to) {
final DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(getDevice());
// If the device does not support REM sleep, we need to exclude its bit from the activity type
int sleepActivityType = ActivityKind.TYPE_SLEEP;
if (!coordinator.supportsRemSleep()) {
sleepActivityType &= ~ActivityKind.TYPE_REM_SLEEP;
}
if (getRawKindSampleProperty() != null) { if (getRawKindSampleProperty() != null) {
return getGBActivitySamples(timestamp_from, timestamp_to, ActivityKind.TYPE_SLEEP); return getGBActivitySamples(timestamp_from, timestamp_to, sleepActivityType);
} else { } else {
return getActivitySamplesByActivityFilter(timestamp_from, timestamp_to, ActivityKind.TYPE_SLEEP); return getActivitySamplesByActivityFilter(timestamp_from, timestamp_to, sleepActivityType);
} }
} }

View File

@ -341,6 +341,11 @@ public interface DeviceCoordinator {
*/ */
boolean supportsRealtimeData(); boolean supportsRealtimeData();
/**
* Indicates whether the device supports REM sleep tracking.
*/
boolean supportsRemSleep();
/** /**
* Indicates whether the device supports current weather and/or weather * Indicates whether the device supports current weather and/or weather
* forecast display. * forecast display.

View File

@ -53,10 +53,11 @@ public class ActivityKind {
public static final int TYPE_STRENGTH_TRAINING = 0x00200000; public static final int TYPE_STRENGTH_TRAINING = 0x00200000;
public static final int TYPE_HIKING = 0x00400000; public static final int TYPE_HIKING = 0x00400000;
public static final int TYPE_CLIMBING = 0x00800000; public static final int TYPE_CLIMBING = 0x00800000;
public static final int TYPE_REM_SLEEP = 0x01000000;
private static final int TYPES_COUNT = 26; private static final int TYPES_COUNT = 26;
public static final int TYPE_SLEEP = TYPE_LIGHT_SLEEP | TYPE_DEEP_SLEEP; public static final int TYPE_SLEEP = TYPE_LIGHT_SLEEP | TYPE_DEEP_SLEEP | TYPE_REM_SLEEP;
public static final int TYPE_ALL = TYPE_ACTIVITY | TYPE_SLEEP | TYPE_NOT_WORN; public static final int TYPE_ALL = TYPE_ACTIVITY | TYPE_SLEEP | TYPE_NOT_WORN;
public static int[] mapToDBActivityTypes(int types, SampleProvider provider) { public static int[] mapToDBActivityTypes(int types, SampleProvider provider) {
@ -131,6 +132,9 @@ public class ActivityKind {
if ((types & ActivityKind.TYPE_STRENGTH_TRAINING) != 0) { if ((types & ActivityKind.TYPE_STRENGTH_TRAINING) != 0) {
result[i++] = provider.toRawActivityKind(TYPE_STRENGTH_TRAINING); result[i++] = provider.toRawActivityKind(TYPE_STRENGTH_TRAINING);
} }
if ((types & ActivityKind.TYPE_REM_SLEEP) != 0) {
result[i++] = provider.toRawActivityKind(TYPE_REM_SLEEP);
}
return Arrays.copyOf(result, i); return Arrays.copyOf(result, i);
} }
@ -199,8 +203,8 @@ public class ActivityKind {
case TYPE_NOT_MEASURED: case TYPE_NOT_MEASURED:
return R.drawable.ic_activity_not_measured; return R.drawable.ic_activity_not_measured;
case TYPE_LIGHT_SLEEP: case TYPE_LIGHT_SLEEP:
return R.drawable.ic_activity_sleep;
case TYPE_DEEP_SLEEP: case TYPE_DEEP_SLEEP:
case TYPE_REM_SLEEP:
return R.drawable.ic_activity_sleep; return R.drawable.ic_activity_sleep;
case TYPE_RUNNING: case TYPE_RUNNING:
return R.drawable.ic_activity_running; return R.drawable.ic_activity_running;

View File

@ -87,22 +87,26 @@ public class DailyTotals {
long[] sleep = getTotalsSleepForActivityAmounts(amountsSleep); long[] sleep = getTotalsSleepForActivityAmounts(amountsSleep);
long steps = getTotalsStepsForActivityAmounts(amountsSteps); long steps = getTotalsStepsForActivityAmounts(amountsSteps);
return new long[]{steps, sleep[0] + sleep[1]}; return new long[]{steps, sleep[0] + sleep[1] + sleep[2]};
} }
private long[] getTotalsSleepForActivityAmounts(ActivityAmounts activityAmounts) { private long[] getTotalsSleepForActivityAmounts(ActivityAmounts activityAmounts) {
long totalSecondsDeepSleep = 0; long totalSecondsDeepSleep = 0;
long totalSecondsLightSleep = 0; long totalSecondsLightSleep = 0;
long totalSecondsRemSleep = 0;
for (ActivityAmount amount : activityAmounts.getAmounts()) { for (ActivityAmount amount : activityAmounts.getAmounts()) {
if (amount.getActivityKind() == ActivityKind.TYPE_DEEP_SLEEP) { if (amount.getActivityKind() == ActivityKind.TYPE_DEEP_SLEEP) {
totalSecondsDeepSleep += amount.getTotalSeconds(); totalSecondsDeepSleep += amount.getTotalSeconds();
} else if (amount.getActivityKind() == ActivityKind.TYPE_LIGHT_SLEEP) { } else if (amount.getActivityKind() == ActivityKind.TYPE_LIGHT_SLEEP) {
totalSecondsLightSleep += amount.getTotalSeconds(); totalSecondsLightSleep += amount.getTotalSeconds();
} else if (amount.getActivityKind() == ActivityKind.TYPE_REM_SLEEP) {
totalSecondsRemSleep += amount.getTotalSeconds();
} }
} }
long totalMinutesDeepSleep = (totalSecondsDeepSleep / 60); long totalMinutesDeepSleep = (totalSecondsDeepSleep / 60);
long totalMinutesLightSleep = (totalSecondsLightSleep / 60); long totalMinutesLightSleep = (totalSecondsLightSleep / 60);
return new long[]{totalMinutesDeepSleep, totalMinutesLightSleep}; long totalMinutesRemSleep = (totalSecondsRemSleep / 60);
return new long[]{totalMinutesDeepSleep, totalMinutesLightSleep, totalMinutesRemSleep};
} }

View File

@ -6,6 +6,7 @@
<attr name="chart_deep_sleep" format="color" /> <attr name="chart_deep_sleep" format="color" />
<attr name="chart_light_sleep" format="color" /> <attr name="chart_light_sleep" format="color" />
<attr name="chart_rem_sleep" format="color" />
<attr name="chart_activity" format="color" /> <attr name="chart_activity" format="color" />
<attr name="chart_not_worn" format="color" /> <attr name="chart_not_worn" format="color" />
<attr name="alternate_row_background" format="color" /> <attr name="alternate_row_background" format="color" />

View File

@ -27,6 +27,9 @@
<color name="chart_light_sleep_light" type="color">#46acea</color> <color name="chart_light_sleep_light" type="color">#46acea</color>
<color name="chart_light_sleep_dark" type="color">#b6bfff</color> <color name="chart_light_sleep_dark" type="color">#b6bfff</color>
<color name="chart_rem_sleep_light" type="color">#b6bfff</color>
<color name="chart_rem_sleep_dark" type="color">#46acea</color>
<color name="chart_activity_light" type="color">#60bd6d</color> <color name="chart_activity_light" type="color">#60bd6d</color>
<color name="chart_activity_dark" type="color">#59b22c</color> <color name="chart_activity_dark" type="color">#59b22c</color>

View File

@ -737,6 +737,7 @@
<string name="abstract_chart_fragment_kind_activity">Activity</string> <string name="abstract_chart_fragment_kind_activity">Activity</string>
<string name="abstract_chart_fragment_kind_light_sleep">Light sleep</string> <string name="abstract_chart_fragment_kind_light_sleep">Light sleep</string>
<string name="abstract_chart_fragment_kind_deep_sleep">Deep sleep</string> <string name="abstract_chart_fragment_kind_deep_sleep">Deep sleep</string>
<string name="abstract_chart_fragment_kind_rem_sleep">REM sleep</string>
<string name="abstract_chart_fragment_kind_not_worn">Not worn</string> <string name="abstract_chart_fragment_kind_not_worn">Not worn</string>
<string name="you_slept">You slept from %1$s to %2$s</string> <string name="you_slept">You slept from %1$s to %2$s</string>
<string name="you_did_not_sleep">You did not sleep</string> <string name="you_did_not_sleep">You did not sleep</string>

View File

@ -14,6 +14,7 @@
<item name="chart_deep_sleep">@color/chart_deep_sleep_light</item> <item name="chart_deep_sleep">@color/chart_deep_sleep_light</item>
<item name="chart_light_sleep">@color/chart_light_sleep_light</item> <item name="chart_light_sleep">@color/chart_light_sleep_light</item>
<item name="chart_rem_sleep">@color/chart_rem_sleep_light</item>
<item name="chart_activity">@color/chart_activity_light</item> <item name="chart_activity">@color/chart_activity_light</item>
<item name="chart_not_worn">@color/chart_not_worn_light</item> <item name="chart_not_worn">@color/chart_not_worn_light</item>
<item name="alternate_row_background">@color/alternate_row_background_light</item> <item name="alternate_row_background">@color/alternate_row_background_light</item>
@ -40,6 +41,7 @@
<item name="chart_deep_sleep">@color/chart_deep_sleep_dark</item> <item name="chart_deep_sleep">@color/chart_deep_sleep_dark</item>
<item name="chart_light_sleep">@color/chart_light_sleep_dark</item> <item name="chart_light_sleep">@color/chart_light_sleep_dark</item>
<item name="chart_rem_sleep">@color/chart_rem_sleep_dark</item>
<item name="chart_activity">@color/chart_activity_dark</item> <item name="chart_activity">@color/chart_activity_dark</item>
<item name="chart_not_worn">@color/chart_not_worn_dark</item> <item name="chart_not_worn">@color/chart_not_worn_dark</item>
<item name="alternate_row_background">@color/alternate_row_background_dark</item> <item name="alternate_row_background">@color/alternate_row_background_dark</item>
@ -63,6 +65,7 @@
<item name="chart_deep_sleep">@color/chart_deep_sleep_dark</item> <item name="chart_deep_sleep">@color/chart_deep_sleep_dark</item>
<item name="chart_light_sleep">@color/chart_light_sleep_dark</item> <item name="chart_light_sleep">@color/chart_light_sleep_dark</item>
<item name="chart_rem_sleep">@color/chart_rem_sleep_dark</item>
<item name="chart_activity">@color/chart_activity_dark</item> <item name="chart_activity">@color/chart_activity_dark</item>
<item name="chart_not_worn">@color/chart_not_worn_dark</item> <item name="chart_not_worn">@color/chart_not_worn_dark</item>
<item name="alternate_row_background">@color/alternate_row_background_dark</item> <item name="alternate_row_background">@color/alternate_row_background_dark</item>