From 00c0a2ba5c98e64b1b7c71075f2faf6c20ee4ee3 Mon Sep 17 00:00:00 2001 From: Gideon Zenz Date: Tue, 2 Jun 2026 17:51:38 +0200 Subject: [PATCH] Xiaomi: Suppress premature activity-data-finish signal during two-phase fetch The Xiaomi two-phase activity fetch (today + past) could fire the data-finish signal that triggers downstream consumers before all DETAILS files had been parsed, so a consumer reading the database in that gap saw an incomplete picture. Hold the finish signal until both fetch phases have delivered and all files are parsed. Use an AtomicBoolean for the fetch-hold flag and set awaitingPastResponse before the fetch starts to avoid signalling between the two phases. --- .../activity/XiaomiActivityFileFetcher.java | 42 ++++++++++++++++--- .../xiaomi/services/XiaomiHealthService.java | 7 ++++ 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/XiaomiActivityFileFetcher.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/XiaomiActivityFileFetcher.java index 385fe63996..48d7bfd434 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/XiaomiActivityFileFetcher.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/activity/XiaomiActivityFileFetcher.java @@ -34,6 +34,7 @@ import java.util.Arrays; import java.util.List; import java.util.PriorityQueue; import java.util.Queue; +import java.util.concurrent.atomic.AtomicBoolean; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; @@ -52,6 +53,8 @@ public class XiaomiActivityFileFetcher { private final Queue mFetchQueue = new PriorityQueue<>(); private ByteArrayOutputStream mBuffer = new ByteArrayOutputStream(); private boolean isFetching = false; + private volatile boolean awaitingPastResponse = false; + private final AtomicBoolean queueHeld = new AtomicBoolean(false); private final Handler timeoutHandler = new Handler(Looper.getMainLooper()); @@ -61,6 +64,8 @@ public class XiaomiActivityFileFetcher { public void dispose() { clearTimeout(); + awaitingPastResponse = false; + queueHeld.set(false); } private void clearTimeout() { @@ -71,6 +76,7 @@ public class XiaomiActivityFileFetcher { // #4305 - Set the timeout in case the watch does not send the file this.timeoutHandler.postDelayed(() -> { LOG.warn("Timed out waiting for activity file with {} bytes in the buffer", mBuffer.size()); + awaitingPastResponse = false; triggerNextFetch(); }, 5000L); } @@ -157,6 +163,30 @@ public class XiaomiActivityFileFetcher { triggerNextFetch(); } + public void setAwaitingPastResponse(final boolean awaiting) { + this.awaitingPastResponse = awaiting; + } + + public boolean isFetching() { + return isFetching; + } + + public void signalComplete() { + LOG.debug("Nothing more to fetch"); + isFetching = false; + queueHeld.set(false); + mHealthService.getSupport().getDevice().unsetBusyTask(); + GB.signalActivityDataFinish(mHealthService.getSupport().getDevice()); + GB.updateTransferNotification(null, "", false, 100, mHealthService.getSupport().getContext()); + mHealthService.getSupport().getDevice().sendDeviceUpdateIntent(mHealthService.getSupport().getContext()); + } + + public void resumeFetching() { + if (queueHeld.compareAndSet(true, false)) { + triggerNextFetch(); + } + } + public void fetch(final List fileIds) { // #4305 - ensure unique files for (final XiaomiActivityFileId fileId : fileIds) { @@ -186,12 +216,12 @@ public class XiaomiActivityFileFetcher { final XiaomiActivityFileId fileId = mFetchQueue.poll(); if (fileId == null) { - LOG.debug("Nothing more to fetch"); - isFetching = false; - mHealthService.getSupport().getDevice().unsetBusyTask(); - GB.signalActivityDataFinish(mHealthService.getSupport().getDevice()); - GB.updateTransferNotification(null, "", false, 100, mHealthService.getSupport().getContext()); - mHealthService.getSupport().getDevice().sendDeviceUpdateIntent(mHealthService.getSupport().getContext()); + if (awaitingPastResponse) { + LOG.debug("Queue empty but awaiting past fetch response, holding signal"); + queueHeld.set(true); + return; + } + signalComplete(); return; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiHealthService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiHealthService.java index 9685496e36..712b4425a1 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiHealthService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/services/XiaomiHealthService.java @@ -869,11 +869,18 @@ public class XiaomiHealthService extends AbstractXiaomiService { } fileIds.add(fileId); } + if (subtype == CMD_ACTIVITY_FETCH_TODAY) { + activityFetcher.setAwaitingPastResponse(true); + } + activityFetcher.fetch(fileIds); if (subtype == CMD_ACTIVITY_FETCH_TODAY) { LOG.debug("Fetch recorded data from the past"); fetchRecordedDataPast(); + } else if (subtype == CMD_ACTIVITY_FETCH_PAST) { + activityFetcher.setAwaitingPastResponse(false); + activityFetcher.resumeFetching(); } }