Dashboard: Use database read lock, enable parallel async tasks

This commit is contained in:
José Rebelo
2026-02-10 20:55:40 +00:00
parent 3e0084e188
commit 1a8a6447e2
11 changed files with 104 additions and 98 deletions
@@ -83,6 +83,7 @@ import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardVO2Max
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardVO2MaxRunningWidget;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.util.CachedValue;
import nodomain.freeyourgadget.gadgetbridge.util.DashboardUtils;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
@@ -382,118 +383,93 @@ public class DashboardFragment extends Fragment implements MenuProvider {
public int timeFrom;
public int timeTo;
public final List<GeneralizedActivity> generalizedActivities = Collections.synchronizedList(new ArrayList<>());
private int stepsTotal;
private float stepsGoalFactor;
private int restingCaloriesTotal;
private int activeCaloriesTotal;
private float activeCaloriesGoalFactor;
private int caloriesTotal;
private long sleepTotalMinutes;
private float sleepGoalFactor;
private float distanceTotalMeters;
private float distanceGoalFactor;
private long activeMinutesTotal;
private float activeMinutesGoalFactor;
private final CachedValue<Integer> stepsTotal = new CachedValue<>();
private final CachedValue<Float> stepsGoalFactor = new CachedValue<>();
private final CachedValue<Integer> restingCaloriesTotal = new CachedValue<>();
private final CachedValue<Integer> activeCaloriesTotal = new CachedValue<>();
private final CachedValue<Float> activeCaloriesGoalFactor = new CachedValue<>();
private final CachedValue<Long> sleepTotalMinutes = new CachedValue<>();
private final CachedValue<Float> sleepGoalFactor = new CachedValue<>();
private final CachedValue<Float> distanceTotalMeters = new CachedValue<>();
private final CachedValue<Float> distanceGoalFactor = new CachedValue<>();
private final CachedValue<Long> activeMinutesTotal = new CachedValue<>();
private final CachedValue<Float> activeMinutesGoalFactor = new CachedValue<>();
private final Map<String, Serializable> genericData = new ConcurrentHashMap<>();
public void clear() {
restingCaloriesTotal = 0;
activeCaloriesTotal = 0;
activeCaloriesGoalFactor = 0;
caloriesTotal = 0;
stepsTotal = 0;
stepsGoalFactor = 0;
sleepTotalMinutes = 0;
sleepGoalFactor = 0;
distanceTotalMeters = 0;
distanceGoalFactor = 0;
activeMinutesTotal = 0;
activeMinutesGoalFactor = 0;
restingCaloriesTotal.clear();
activeCaloriesTotal.clear();
activeCaloriesGoalFactor.clear();
stepsTotal.clear();
stepsGoalFactor.clear();
sleepTotalMinutes.clear();
sleepGoalFactor.clear();
distanceTotalMeters.clear();
distanceGoalFactor.clear();
activeMinutesTotal.clear();
activeMinutesGoalFactor.clear();
generalizedActivities.clear();
genericData.clear();
}
public boolean isEmpty() {
return (stepsTotal == 0 &&
stepsGoalFactor == 0 &&
restingCaloriesTotal == 0 &&
activeCaloriesTotal == 0 &&
activeCaloriesGoalFactor == 0 &&
caloriesTotal == 0 &&
sleepTotalMinutes == 0 &&
sleepGoalFactor == 0 &&
distanceTotalMeters == 0 &&
distanceGoalFactor == 0 &&
activeMinutesTotal == 0 &&
activeMinutesGoalFactor == 0 &&
return (stepsTotal.isEmpty() &&
stepsGoalFactor.isEmpty() &&
restingCaloriesTotal.isEmpty() &&
activeCaloriesTotal.isEmpty() &&
activeCaloriesGoalFactor.isEmpty() &&
sleepTotalMinutes.isEmpty() &&
sleepGoalFactor.isEmpty() &&
distanceTotalMeters.isEmpty() &&
distanceGoalFactor.isEmpty() &&
activeMinutesTotal.isEmpty() &&
activeMinutesGoalFactor.isEmpty() &&
genericData.isEmpty() &&
generalizedActivities.isEmpty());
}
public synchronized int getStepsTotal() {
if (stepsTotal == 0)
stepsTotal = DashboardUtils.getStepsTotal(this);
return stepsTotal;
public int getStepsTotal() {
return stepsTotal.get(() -> DashboardUtils.getStepsTotal(this));
}
public synchronized float getStepsGoalFactor() {
if (stepsGoalFactor == 0)
stepsGoalFactor = DashboardUtils.getStepsGoalFactor(this);
return stepsGoalFactor;
public float getStepsGoalFactor() {
return stepsGoalFactor.get(() -> DashboardUtils.getStepsGoalFactor(this));
}
public synchronized float getDistanceTotal() {
if (distanceTotalMeters == 0)
distanceTotalMeters = DashboardUtils.getDistanceTotal(this);
return distanceTotalMeters;
public float getDistanceTotal() {
return distanceTotalMeters.get(() -> DashboardUtils.getDistanceTotal(this));
}
public synchronized float getDistanceGoalFactor() {
if (distanceGoalFactor == 0)
distanceGoalFactor = DashboardUtils.getDistanceGoalFactor(this);
return distanceGoalFactor;
public float getDistanceGoalFactor() {
return distanceGoalFactor.get(() -> DashboardUtils.getDistanceGoalFactor(this));
}
public synchronized long getActiveMinutesTotal() {
if (activeMinutesTotal == 0)
activeMinutesTotal = DashboardUtils.getActiveMinutesTotal(this);
return activeMinutesTotal;
public long getActiveMinutesTotal() {
return activeMinutesTotal.get(() -> DashboardUtils.getActiveMinutesTotal(this));
}
public synchronized float getActiveMinutesGoalFactor() {
if (activeMinutesGoalFactor == 0)
activeMinutesGoalFactor = DashboardUtils.getActiveMinutesGoalFactor(this);
return activeMinutesGoalFactor;
public float getActiveMinutesGoalFactor() {
return activeMinutesGoalFactor.get(() -> DashboardUtils.getActiveMinutesGoalFactor(this));
}
public synchronized long getSleepMinutesTotal() {
if (sleepTotalMinutes == 0)
sleepTotalMinutes = DashboardUtils.getSleepMinutesTotal(this);
return sleepTotalMinutes;
public long getSleepMinutesTotal() {
return sleepTotalMinutes.get(() -> DashboardUtils.getSleepMinutesTotal(this));
}
public synchronized float getSleepMinutesGoalFactor() {
if (sleepGoalFactor == 0)
sleepGoalFactor = DashboardUtils.getSleepMinutesGoalFactor(this);
return sleepGoalFactor;
public float getSleepMinutesGoalFactor() {
return sleepGoalFactor.get(() -> DashboardUtils.getSleepMinutesGoalFactor(this));
}
public synchronized int getActiveCaloriesTotal() {
if (activeCaloriesTotal == 0)
activeCaloriesTotal = DashboardUtils.getActiveCaloriesTotal(this);
return activeCaloriesTotal;
public int getActiveCaloriesTotal() {
return activeCaloriesTotal.get(() -> DashboardUtils.getActiveCaloriesTotal(this));
}
public synchronized int getRestingCaloriesTotal() {
if (restingCaloriesTotal == 0)
restingCaloriesTotal = DashboardUtils.getRestingCaloriesTotal(this);
return restingCaloriesTotal;
public int getRestingCaloriesTotal() {
return restingCaloriesTotal.get(() -> DashboardUtils.getRestingCaloriesTotal(this));
}
public synchronized float getActiveCaloriesGoalFactor() {
if (activeCaloriesGoalFactor == 0)
activeCaloriesGoalFactor = DashboardUtils.getActiveCaloriesGoalFactor(this);
return activeCaloriesGoalFactor;
public float getActiveCaloriesGoalFactor() {
return activeCaloriesGoalFactor.get(() -> DashboardUtils.getActiveCaloriesGoalFactor(this));
}
public void put(final String key, final Serializable value) {
@@ -36,7 +36,7 @@ public abstract class AbstractDashboardVO2MaxWidget extends AbstractGaugeWidget
// Latest vo2max sample.
Vo2MaxSample sample = null;
try (DBHandler dbHandler = GBApplication.acquireDB()) {
try (DBHandler dbHandler = GBApplication.acquireDbReadOnly()) {
for (GBDevice dev : devices) {
final Vo2MaxSampleProvider sampleProvider = (Vo2MaxSampleProvider) dev.getDeviceCoordinator().getVo2MaxSampleProvider(dev, dbHandler.getDaoSession());
final Vo2MaxSample latestSample = sampleProvider.getLatestSample(getVO2MaxType(), dashboardData.timeTo * 1000L);
@@ -82,9 +82,10 @@ public abstract class AbstractGaugeWidget extends AbstractDashboardWidget {
@Override
protected void fillData() {
if (gaugeBar == null) return;
LOG.trace("Starting fillData for {}", getClass().getSimpleName());
gaugeBar.post(() -> {
final FillDataAsyncTask myAsyncTask = new FillDataAsyncTask();
myAsyncTask.execute();
myAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
});
}
@@ -71,7 +71,7 @@ public class DashboardBodyEnergyWidget extends AbstractGaugeWidget {
// Latest stress sample for today
BodyEnergySample sample = null;
try (DBHandler dbHandler = GBApplication.acquireDB()) {
try (DBHandler dbHandler = GBApplication.acquireDbReadOnly()) {
for (GBDevice dev : devices) {
final BodyEnergySample latestSample = dev.getDeviceCoordinator().getBodyEnergySampleProvider(dev, dbHandler.getDaoSession())
.getLatestSample();
@@ -90,7 +90,7 @@ public class DashboardBodyEnergyWidget extends AbstractGaugeWidget {
}
} else {
// Gain / loss for the period
try (DBHandler dbHandler = GBApplication.acquireDB()) {
try (DBHandler dbHandler = GBApplication.acquireDbReadOnly()) {
for (GBDevice dev : devices) {
if ((dashboardData.showAllDevices || dashboardData.showDeviceList.contains(dev.getAddress())) && dev.getDeviceCoordinator().supportsBodyEnergy(dev)) {
final List<? extends BodyEnergySample> samples = dev.getDeviceCoordinator()
@@ -108,11 +108,12 @@ public class DashboardGoalsWidget extends AbstractDashboardWidget {
@Override
protected void fillData() {
if (goalsView == null) return;
LOG.trace("Starting fillData for {}", getClass().getSimpleName());
goalsView.post(new Runnable() {
@Override
public void run() {
FillDataAsyncTask myAsyncTask = new FillDataAsyncTask();
myAsyncTask.execute();
myAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
});
}
@@ -60,7 +60,7 @@ public class DashboardHrvWidget extends AbstractGaugeWidget {
HrvSummarySample latestSummary = null;
try (DBHandler dbHandler = GBApplication.acquireDB()) {
try (DBHandler dbHandler = GBApplication.acquireDbReadOnly()) {
for (GBDevice dev : devices) {
final TimeSampleProvider<? extends HrvSummarySample> hrvSummarySampleProvider = dev.getDeviceCoordinator().getHrvSummarySampleProvider(dev, dbHandler.getDaoSession());
@@ -64,7 +64,7 @@ public class DashboardSleepScoreWidget extends AbstractGaugeWidget {
final SleepScoreData data = new SleepScoreData();
SleepScoreSample sample = null;
try (DBHandler dbHandler = GBApplication.acquireDB()) {
try (DBHandler dbHandler = GBApplication.acquireDbReadOnly()) {
for (GBDevice dev : devices) {
TimeSampleProvider<? extends SleepScoreSample> provider = dev.getDeviceCoordinator().getSleepScoreProvider(dev, dbHandler.getDaoSession());
final SleepScoreSample latestSample = provider.getLatestSample(dashboardData.timeTo * 1000L);
@@ -387,6 +387,8 @@ public class DashboardTodayWidget extends AbstractDashboardWidget {
protected void fillData() {
if (todayView == null) return;
LOG.trace("Starting fillData for {}", getClass().getSimpleName());
Prefs prefs = GBApplication.getPrefs();
if (prefs.getBoolean("dashboard_widget_today_show_yesterday", false)) {
Calendar today = Calendar.getInstance();
@@ -401,7 +403,7 @@ public class DashboardTodayWidget extends AbstractDashboardWidget {
@Override
public void run() {
FillDataAsyncTask myAsyncTask = new FillDataAsyncTask();
myAsyncTask.execute();
myAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
});
}
@@ -547,7 +549,7 @@ public class DashboardTodayWidget extends AbstractDashboardWidget {
List<ActivitySample> allActivitySamples = new ArrayList<>();
List<ActivitySession> stepSessions = new ArrayList<>();
List<BaseActivitySummary> activitySummaries = null;
try (DBHandler dbHandler = GBApplication.acquireDB()) {
try (DBHandler dbHandler = GBApplication.acquireDbReadOnly()) {
final List<Long> deviceIds = new LinkedList<>();
for (GBDevice dev : devices) {
if ((dashboardData.showAllDevices || dashboardData.showDeviceList.contains(dev.getAddress())) && dev.getDeviceCoordinator().supportsActivityTracking(dev)) {
@@ -44,7 +44,7 @@ public class DashboardStressData implements Serializable {
final int[] totalTime = new int[StressDailyFragment.StressType.values().length];
try (DBHandler dbHandler = GBApplication.acquireDB()) {
try (DBHandler dbHandler = GBApplication.acquireDbReadOnly()) {
for (GBDevice dev : devices) {
if ((dashboardData.showAllDevices || dashboardData.showDeviceList.contains(dev.getAddress())) && dev.getDeviceCoordinator().supportsStressMeasurement(dev)) {
final List<? extends StressSample> samples = dev.getDeviceCoordinator()
@@ -0,0 +1,27 @@
package nodomain.freeyourgadget.gadgetbridge.util;
import java.io.Serializable;
import java.util.function.Supplier;
public class CachedValue<V extends Serializable> implements Serializable {
private volatile V value = null;
public synchronized V get(final Supplier<V> supplier) {
if (value == null) {
value = supplier.get();
}
return value;
}
public synchronized void clear() {
value = null;
}
public synchronized boolean hasValue() {
return value != null;
}
public synchronized boolean isEmpty() {
return value == null;
}
}
@@ -52,7 +52,7 @@ public class DashboardUtils {
public static int getStepsTotal(DashboardFragment.DashboardData dashboardData) {
List<GBDevice> devices = GBApplication.app().getDeviceManager().getDevices();
int totalSteps = 0;
try (DBHandler dbHandler = GBApplication.acquireDB()) {
try (DBHandler dbHandler = GBApplication.acquireDbReadOnly()) {
for (GBDevice dev : devices) {
if ((dashboardData.showAllDevices || dashboardData.showDeviceList.contains(dev.getAddress())) && dev.getDeviceCoordinator().supportsStepCounter(dev)) {
totalSteps += (int) getDailyTotals(dev, dbHandler, dashboardData.timeTo).getSteps();
@@ -67,7 +67,7 @@ public class DashboardUtils {
public static int getActiveCaloriesTotal(DashboardFragment.DashboardData dashboardData) {
List<GBDevice> devices = GBApplication.app().getDeviceManager().getDevices();
int totalActiveCalories = 0;
try (DBHandler dbHandler = GBApplication.acquireDB()) {
try (DBHandler dbHandler = GBApplication.acquireDbReadOnly()) {
for (GBDevice dev : devices) {
if ((dashboardData.showAllDevices || dashboardData.showDeviceList.contains(dev.getAddress())) && dev.getDeviceCoordinator().supportsActiveCalories(dev)) {
totalActiveCalories += (int) getDailyTotals(dev, dbHandler, dashboardData.timeTo).getActiveCalories();
@@ -84,7 +84,7 @@ public class DashboardUtils {
List<GBDevice> devices = GBApplication.app().getDeviceManager().getDevices();
int totalRestingCalories = 0;
int totalRestingCaloriesDevices = 0;
try (DBHandler dbHandler = GBApplication.acquireDB()) {
try (DBHandler dbHandler = GBApplication.acquireDbReadOnly()) {
for (GBDevice dev : devices) {
if ((dashboardData.showAllDevices || dashboardData.showDeviceList.contains(dev.getAddress())) && dev.getDeviceCoordinator().supportsActiveCalories(dev)) {
final int restingCalories = (int) getDailyTotals(dev, dbHandler, dashboardData.timeTo).getRestingCalories();
@@ -121,7 +121,7 @@ public class DashboardUtils {
public static long getSleepMinutesTotal(DashboardFragment.DashboardData dashboardData) {
List<GBDevice> devices = GBApplication.app().getDeviceManager().getDevices();
long totalSleepMinutes = 0;
try (DBHandler dbHandler = GBApplication.acquireDB()) {
try (DBHandler dbHandler = GBApplication.acquireDbReadOnly()) {
for (GBDevice dev : devices) {
if ((dashboardData.showAllDevices || dashboardData.showDeviceList.contains(dev.getAddress())) && dev.getDeviceCoordinator().supportsSleepMeasurement(dev)) {
totalSleepMinutes += getSleep(dev, dbHandler, dashboardData.timeTo);
@@ -145,10 +145,9 @@ public class DashboardUtils {
public static float getDistanceTotal(DashboardFragment.DashboardData dashboardData) {
ActivityUser activityUser = new ActivityUser();
int stepLength = activityUser.getStepLengthCm();
List<GBDevice> devices = GBApplication.app().getDeviceManager().getDevices();
long totalDistanceCm = 0;
try (DBHandler dbHandler = GBApplication.acquireDB()) {
try (DBHandler dbHandler = GBApplication.acquireDbReadOnly()) {
for (GBDevice dev : devices) {
if ((dashboardData.showAllDevices || dashboardData.showDeviceList.contains(dev.getAddress())) && dev.getDeviceCoordinator().supportsStepCounter(dev)) {
final DailyTotals dailyTotals = getDailyTotals(dev, dbHandler, dashboardData.timeTo);
@@ -186,7 +185,7 @@ public class DashboardUtils {
public static long getActiveMinutesTotal(DashboardFragment.DashboardData dashboardData) {
List<GBDevice> devices = GBApplication.app().getDeviceManager().getDevices();
long totalActiveMinutes = 0;
try (DBHandler dbHandler = GBApplication.acquireDB()) {
try (DBHandler dbHandler = GBApplication.acquireDbReadOnly()) {
for (GBDevice dev : devices) {
if ((dashboardData.showAllDevices || dashboardData.showDeviceList.contains(dev.getAddress())) && dev.getDeviceCoordinator().supportsStepCounter(dev)) {
totalActiveMinutes += getActiveMinutes(dev, dbHandler, dashboardData);
@@ -216,7 +215,7 @@ public class DashboardUtils {
boolean isEmptySummary = false;
if (activitySamples != null) {
stepSessions = stepAnalysis.calculateStepSessions(activitySamples);
if (stepSessions.size() == 0) {
if (stepSessions.isEmpty()) {
isEmptySummary = true;
}
stepSessionsSummary = stepAnalysis.calculateSummary(stepSessions, isEmptySummary);