mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
Garmin: Display fetch and parse progress
This commit is contained in:
+6
@@ -120,6 +120,8 @@ public CreateFileMessage initiateUpload(byte[] fileAsByteArray, FileType.FILETYP
|
|||||||
currentlyDownloading.append(fileTransferDataMessage);
|
currentlyDownloading.append(fileTransferDataMessage);
|
||||||
if (!currentlyDownloading.dataHolder.hasRemaining())
|
if (!currentlyDownloading.dataHolder.hasRemaining())
|
||||||
processCompleteDownload();
|
processCompleteDownload();
|
||||||
|
else
|
||||||
|
deviceSupport.onFileDownloadProgress(currentlyDownloading.dataHolder.position());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processCompleteDownload() {
|
private void processCompleteDownload() {
|
||||||
@@ -397,6 +399,10 @@ public CreateFileMessage initiateUpload(byte[] fileAsByteArray, FileType.FILETYP
|
|||||||
return fileDate;
|
return fileDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getFileSize() {
|
||||||
|
return fileSize;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the output path.
|
* Builds the output path.
|
||||||
* Format: [FILE_TYPE]/[YEAR]/[FILE_TYPE]_[yyyy-MM-dd_HH-mm-ss]_[INDEX].[fit/bin]
|
* Format: [FILE_TYPE]/[YEAR]/[FILE_TYPE]_[yyyy-MM-dd_HH-mm-ss]_[INDEX].[fit/bin]
|
||||||
|
|||||||
+29
-26
@@ -93,6 +93,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.MediaManager;
|
import nodomain.freeyourgadget.gadgetbridge.util.MediaManager;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.notifications.GBProgressNotification;
|
||||||
|
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_ALLOW_HIGH_MTU;
|
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_ALLOW_HIGH_MTU;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SEND_APP_NOTIFICATIONS;
|
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SEND_APP_NOTIFICATIONS;
|
||||||
@@ -111,6 +112,8 @@ public class GarminSupport extends AbstractBTLESingleDeviceSupport implements IC
|
|||||||
private boolean mFirstConnect = false;
|
private boolean mFirstConnect = false;
|
||||||
private boolean isBusyFetching;
|
private boolean isBusyFetching;
|
||||||
|
|
||||||
|
private GBProgressNotification transferNotification;
|
||||||
|
|
||||||
final Map<UUID, GdiInstalledAppsService.InstalledAppsService.InstalledApp> installedApps = new HashMap<>();
|
final Map<UUID, GdiInstalledAppsService.InstalledAppsService.InstalledApp> installedApps = new HashMap<>();
|
||||||
|
|
||||||
public GarminSupport() {
|
public GarminSupport() {
|
||||||
@@ -132,6 +135,7 @@ public class GarminSupport extends AbstractBTLESingleDeviceSupport implements IC
|
|||||||
public void setContext(final GBDevice gbDevice, final BluetoothAdapter btAdapter, final Context context) {
|
public void setContext(final GBDevice gbDevice, final BluetoothAdapter btAdapter, final Context context) {
|
||||||
super.setContext(gbDevice, btAdapter, context);
|
super.setContext(gbDevice, btAdapter, context);
|
||||||
this.mediaManager = new MediaManager(context);
|
this.mediaManager = new MediaManager(context);
|
||||||
|
this.transferNotification = new GBProgressNotification(context, GB.NOTIFICATION_CHANNEL_ID_TRANSFER);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -143,8 +147,15 @@ public class GarminSupport extends AbstractBTLESingleDeviceSupport implements IC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onFileDownloadProgress(final int progress) {
|
||||||
|
transferNotification.setChunkProgress(progress);
|
||||||
|
}
|
||||||
|
|
||||||
public void addFileToDownloadList(FileTransferHandler.DirectoryEntry directoryEntry) {
|
public void addFileToDownloadList(FileTransferHandler.DirectoryEntry directoryEntry) {
|
||||||
filesToDownload.add(directoryEntry);
|
filesToDownload.add(directoryEntry);
|
||||||
|
if (directoryEntry.getFiletype() != FileType.FILETYPE.DIRECTORY) {
|
||||||
|
transferNotification.incrementTotalSize(directoryEntry.getFileSize());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -332,13 +343,13 @@ public class GarminSupport extends AbstractBTLESingleDeviceSupport implements IC
|
|||||||
final FileTransferHandler.DirectoryEntry entry = ((FileDownloadedDeviceEvent) deviceEvent).directoryEntry;
|
final FileTransferHandler.DirectoryEntry entry = ((FileDownloadedDeviceEvent) deviceEvent).directoryEntry;
|
||||||
final String filename = entry.getFileName();
|
final String filename = entry.getFileName();
|
||||||
LOG.debug("FILE DOWNLOAD COMPLETE {}", filename);
|
LOG.debug("FILE DOWNLOAD COMPLETE {}", filename);
|
||||||
|
transferNotification.incrementTotalProgress(entry.getFileSize());
|
||||||
|
|
||||||
if (entry.getFiletype().isFitFile()) {
|
if (entry.getFiletype().isFitFile()) {
|
||||||
try (DBHandler handler = GBApplication.acquireDB()) {
|
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||||
final DaoSession session = handler.getDaoSession();
|
final DaoSession session = handler.getDaoSession();
|
||||||
|
|
||||||
final PendingFileProvider pendingFileProvider = new PendingFileProvider(gbDevice, session);
|
final PendingFileProvider pendingFileProvider = new PendingFileProvider(gbDevice, session);
|
||||||
|
|
||||||
pendingFileProvider.addPendingFile(((FileDownloadedDeviceEvent) deviceEvent).localPath);
|
pendingFileProvider.addPendingFile(((FileDownloadedDeviceEvent) deviceEvent).localPath);
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
GB.toast(getContext(), "Error saving pending file", Toast.LENGTH_LONG, GB.ERROR, e);
|
GB.toast(getContext(), "Error saving pending file", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||||
@@ -617,7 +628,7 @@ public class GarminSupport extends AbstractBTLESingleDeviceSupport implements IC
|
|||||||
if (!filesToDownload.isEmpty() && !fileTransferHandler.isDownloading()) {
|
if (!filesToDownload.isEmpty() && !fileTransferHandler.isDownloading()) {
|
||||||
if (!gbDevice.isBusy()) {
|
if (!gbDevice.isBusy()) {
|
||||||
isBusyFetching = true;
|
isBusyFetching = true;
|
||||||
GB.updateTransferNotification(getContext().getString(R.string.busy_task_fetch_activity_data), "", true, 0, getContext());
|
transferNotification.start(R.string.busy_task_fetch_activity_data, 0);
|
||||||
getDevice().setBusyTask(R.string.busy_task_fetch_activity_data, getContext());
|
getDevice().setBusyTask(R.string.busy_task_fetch_activity_data, getContext());
|
||||||
getDevice().sendDeviceUpdateIntent(getContext());
|
getDevice().sendDeviceUpdateIntent(getContext());
|
||||||
}
|
}
|
||||||
@@ -629,11 +640,17 @@ public class GarminSupport extends AbstractBTLESingleDeviceSupport implements IC
|
|||||||
if (!getKeepActivityDataOnDevice()) { // delete file from watch if already downloaded
|
if (!getKeepActivityDataOnDevice()) { // delete file from watch if already downloaded
|
||||||
sendOutgoingMessage("archive file " + directoryEntry.getFileIndex(), new SetFileFlagsMessage(directoryEntry.getFileIndex(), SetFileFlagsMessage.FileFlags.ARCHIVE));
|
sendOutgoingMessage("archive file " + directoryEntry.getFileIndex(), new SetFileFlagsMessage(directoryEntry.getFileIndex(), SetFileFlagsMessage.FileFlags.ARCHIVE));
|
||||||
}
|
}
|
||||||
|
if (directoryEntry.getFiletype() != FileType.FILETYPE.DIRECTORY) {
|
||||||
|
transferNotification.incrementTotalProgress(directoryEntry.getFileSize());
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
final DownloadRequestMessage downloadRequestMessage = fileTransferHandler.downloadDirectoryEntry(directoryEntry);
|
final DownloadRequestMessage downloadRequestMessage = fileTransferHandler.downloadDirectoryEntry(directoryEntry);
|
||||||
LOG.debug("Will download file: {}", directoryEntry.getOutputPath());
|
LOG.debug("Will download file: {}", directoryEntry.getOutputPath());
|
||||||
|
if (directoryEntry.getFiletype() != FileType.FILETYPE.DIRECTORY) {
|
||||||
|
transferNotification.setChunkProgress(0);
|
||||||
|
}
|
||||||
sendOutgoingMessage("download file " + directoryEntry.getFileIndex(), downloadRequestMessage);
|
sendOutgoingMessage("download file " + directoryEntry.getFileIndex(), downloadRequestMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -661,7 +678,7 @@ public class GarminSupport extends AbstractBTLESingleDeviceSupport implements IC
|
|||||||
if (gbDevice.isBusy() && isBusyFetching) {
|
if (gbDevice.isBusy() && isBusyFetching) {
|
||||||
getDevice().unsetBusyTask();
|
getDevice().unsetBusyTask();
|
||||||
GB.signalActivityDataFinish(getDevice());
|
GB.signalActivityDataFinish(getDevice());
|
||||||
GB.updateTransferNotification(null, "", false, 100, getContext());
|
transferNotification.finish();
|
||||||
getDevice().sendDeviceUpdateIntent(getContext());
|
getDevice().sendDeviceUpdateIntent(getContext());
|
||||||
}
|
}
|
||||||
isBusyFetching = false;
|
isBusyFetching = false;
|
||||||
@@ -672,27 +689,21 @@ public class GarminSupport extends AbstractBTLESingleDeviceSupport implements IC
|
|||||||
// isBusyFetching so we do not start multiple processors
|
// isBusyFetching so we do not start multiple processors
|
||||||
isBusyFetching = false;
|
isBusyFetching = false;
|
||||||
|
|
||||||
|
transferNotification.start(R.string.busy_task_processing_files, 0);
|
||||||
|
transferNotification.setTotalSize(filesToProcess.size());
|
||||||
|
|
||||||
final FitAsyncProcessor fitAsyncProcessor = new FitAsyncProcessor(getContext(), getDevice());
|
final FitAsyncProcessor fitAsyncProcessor = new FitAsyncProcessor(getContext(), getDevice());
|
||||||
final long[] lastNotificationUpdateTs = new long[]{System.currentTimeMillis()};
|
|
||||||
fitAsyncProcessor.process(filesToProcess, new FitAsyncProcessor.Callback() {
|
fitAsyncProcessor.process(filesToProcess, new FitAsyncProcessor.Callback() {
|
||||||
@Override
|
@Override
|
||||||
public void onProgress(final int i) {
|
public void onProgress(final int i) {
|
||||||
final long now = System.currentTimeMillis();
|
transferNotification.setTotalProgress(i);
|
||||||
if (now - lastNotificationUpdateTs[0] > 1500L) {
|
|
||||||
lastNotificationUpdateTs[0] = now;
|
|
||||||
GB.updateTransferNotification(
|
|
||||||
"Parsing fit files", "File " + i + " of " + filesToProcess.size(),
|
|
||||||
true,
|
|
||||||
(i * 100) / filesToProcess.size(), getContext()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFinish() {
|
public void onFinish() {
|
||||||
getDevice().unsetBusyTask();
|
getDevice().unsetBusyTask();
|
||||||
GB.signalActivityDataFinish(getDevice());
|
GB.signalActivityDataFinish(getDevice());
|
||||||
GB.updateTransferNotification(null, "", false, 100, getContext());
|
transferNotification.finish();
|
||||||
getDevice().sendDeviceUpdateIntent(getContext());
|
getDevice().sendDeviceUpdateIntent(getContext());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1053,7 +1064,8 @@ public class GarminSupport extends AbstractBTLESingleDeviceSupport implements IC
|
|||||||
|
|
||||||
GB.toast(getContext(), "Check notification for progress", Toast.LENGTH_LONG, GB.INFO);
|
GB.toast(getContext(), "Check notification for progress", Toast.LENGTH_LONG, GB.INFO);
|
||||||
|
|
||||||
GB.updateTransferNotification("Parsing fit files", "...", true, 0, getContext());
|
transferNotification.start(R.string.busy_task_processing_files, 0);
|
||||||
|
transferNotification.setTotalSize(fitFiles.size());
|
||||||
|
|
||||||
//try (DBHandler handler = GBApplication.acquireDB()) {
|
//try (DBHandler handler = GBApplication.acquireDB()) {
|
||||||
// final DaoSession session = handler.getDaoSession();
|
// final DaoSession session = handler.getDaoSession();
|
||||||
@@ -1063,26 +1075,17 @@ public class GarminSupport extends AbstractBTLESingleDeviceSupport implements IC
|
|||||||
// GB.toast(getContext(), "Error deleting activity data", Toast.LENGTH_LONG, GB.ERROR, e);
|
// GB.toast(getContext(), "Error deleting activity data", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||||
//}
|
//}
|
||||||
|
|
||||||
final long[] lastNotificationUpdateTs = new long[]{System.currentTimeMillis()};
|
|
||||||
final FitAsyncProcessor fitAsyncProcessor = new FitAsyncProcessor(getContext(), getDevice());
|
final FitAsyncProcessor fitAsyncProcessor = new FitAsyncProcessor(getContext(), getDevice());
|
||||||
fitAsyncProcessor.process(fitFiles, new FitAsyncProcessor.Callback() {
|
fitAsyncProcessor.process(fitFiles, new FitAsyncProcessor.Callback() {
|
||||||
@Override
|
@Override
|
||||||
public void onProgress(final int i) {
|
public void onProgress(final int i) {
|
||||||
final long now = System.currentTimeMillis();
|
transferNotification.setTotalProgress(i);
|
||||||
if (now - lastNotificationUpdateTs[0] > 1500L) {
|
|
||||||
lastNotificationUpdateTs[0] = now;
|
|
||||||
GB.updateTransferNotification(
|
|
||||||
"Parsing fit files", "File " + i + " of " + fitFiles.size(),
|
|
||||||
true,
|
|
||||||
(i * 100) / fitFiles.size(), getContext()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFinish() {
|
public void onFinish() {
|
||||||
parsingFitFilesFromStorage = false;
|
parsingFitFilesFromStorage = false;
|
||||||
GB.updateTransferNotification("", "", false, 100, getContext());
|
transferNotification.finish();
|
||||||
GB.signalActivityDataFinish(getDevice());
|
GB.signalActivityDataFinish(getDevice());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
-82
@@ -1,82 +0,0 @@
|
|||||||
/* 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 <https://www.gnu.org/licenses/>. */
|
|
||||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.test;
|
|
||||||
|
|
||||||
import android.os.Handler;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.test.TestDeviceCoordinator;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.test.TestFeature;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport;
|
|
||||||
|
|
||||||
public class TestDeviceSupport extends AbstractDeviceSupport {
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(TestDeviceSupport.class);
|
|
||||||
|
|
||||||
private final Handler handler = new Handler();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean connect() {
|
|
||||||
LOG.info("Connecting");
|
|
||||||
|
|
||||||
getDevice().setUpdateState(GBDevice.State.CONNECTING, getContext());
|
|
||||||
|
|
||||||
handler.postDelayed(() -> {
|
|
||||||
LOG.info("Initialized");
|
|
||||||
|
|
||||||
getDevice().setFirmwareVersion("1.0.0");
|
|
||||||
getDevice().setFirmwareVersion2("N/A");
|
|
||||||
getDevice().setModel("0.1.7");
|
|
||||||
|
|
||||||
if (getCoordinator().supportsLedColor()) {
|
|
||||||
getDevice().setExtraInfo("led_color", 0xff3061e3);
|
|
||||||
} else {
|
|
||||||
getDevice().setExtraInfo("led_color", null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getCoordinator().supports(getDevice(), TestFeature.FM_FREQUENCY)) {
|
|
||||||
getDevice().setExtraInfo("fm_frequency", 90.2f);
|
|
||||||
} else {
|
|
||||||
getDevice().setExtraInfo("fm_frequency", null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO battery percentages
|
|
||||||
// TODO hr measurements
|
|
||||||
// TODO app list
|
|
||||||
// TODO screenshots
|
|
||||||
|
|
||||||
getDevice().setUpdateState(GBDevice.State.INITIALIZED, getContext());
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void dispose() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean useAutoConnect() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected TestDeviceCoordinator getCoordinator() {
|
|
||||||
return (TestDeviceCoordinator) getDevice().getDeviceCoordinator();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+107
@@ -0,0 +1,107 @@
|
|||||||
|
/* Copyright (C) 2025 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 <https://www.gnu.org/licenses/>. */
|
||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.test
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothAdapter
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Handler
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.R
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.test.TestDeviceCoordinator
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.test.TestFeature
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.GB
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.notifications.GBProgressNotification
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
|
open class TestDeviceSupport : AbstractDeviceSupport() {
|
||||||
|
private lateinit var progressNotification: GBProgressNotification
|
||||||
|
private val handler = Handler()
|
||||||
|
|
||||||
|
override fun setContext(gbDevice: GBDevice, btAdapter: BluetoothAdapter, context: Context) {
|
||||||
|
super.setContext(gbDevice, btAdapter, context)
|
||||||
|
progressNotification = GBProgressNotification(context, GB.NOTIFICATION_CHANNEL_ID_TRANSFER)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun connect(): Boolean {
|
||||||
|
LOG.info("Connecting")
|
||||||
|
|
||||||
|
device.setUpdateState(GBDevice.State.CONNECTING, context)
|
||||||
|
|
||||||
|
handler.postDelayed({
|
||||||
|
LOG.info("Initialized")
|
||||||
|
device.firmwareVersion = "1.0.0"
|
||||||
|
device.firmwareVersion2 = "N/A"
|
||||||
|
device.model = "0.1.7"
|
||||||
|
|
||||||
|
if (this.coordinator.supportsLedColor()) {
|
||||||
|
device.setExtraInfo("led_color", 0xff3061e3.toInt())
|
||||||
|
} else {
|
||||||
|
device.setExtraInfo("led_color", null)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.coordinator.supports(device, TestFeature.FM_FREQUENCY)) {
|
||||||
|
device.setExtraInfo("fm_frequency", 90.2f)
|
||||||
|
} else {
|
||||||
|
device.setExtraInfo("fm_frequency", null)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO battery percentages
|
||||||
|
// TODO hr measurements
|
||||||
|
// TODO app list
|
||||||
|
// TODO screenshots
|
||||||
|
|
||||||
|
device.setUpdateState(GBDevice.State.INITIALIZED, context)
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispose() {
|
||||||
|
handler.removeCallbacksAndMessages(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun useAutoConnect(): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFetchRecordedData(dataTypes: Int) {
|
||||||
|
LOG.debug("onFetchRecordedData - simulating progress")
|
||||||
|
|
||||||
|
progressNotification.start(R.string.busy_task_fetch_activity_data, 0)
|
||||||
|
device.setBusyTask(R.string.busy_task_fetch_activity_data, context)
|
||||||
|
device.sendDeviceUpdateIntent(context)
|
||||||
|
progressNotification.setTotalSize(100)
|
||||||
|
handler.postDelayed({ progressNotification.setChunkProgress(20) }, 1000L)
|
||||||
|
handler.postDelayed({ progressNotification.setChunkProgress(40) }, 2000L)
|
||||||
|
handler.postDelayed({ progressNotification.setTotalProgress(60) }, 3000L)
|
||||||
|
handler.postDelayed({ progressNotification.setTotalProgress(80) }, 4000L)
|
||||||
|
handler.postDelayed({
|
||||||
|
progressNotification.finish()
|
||||||
|
device.unsetBusyTask()
|
||||||
|
device.sendDeviceUpdateIntent(context)
|
||||||
|
}, 5000L)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected val coordinator: TestDeviceCoordinator
|
||||||
|
get() = device.deviceCoordinator as TestDeviceCoordinator
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val LOG: Logger = LoggerFactory.getLogger(TestDeviceSupport::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -103,7 +103,6 @@ public class GB {
|
|||||||
/** Commands related to the progress (bar) on the screen */
|
/** Commands related to the progress (bar) on the screen */
|
||||||
public static final String ACTION_SET_PROGRESS_BAR = "GB_Set_Progress_Bar";
|
public static final String ACTION_SET_PROGRESS_BAR = "GB_Set_Progress_Bar";
|
||||||
public static final String PROGRESS_BAR_INDETERMINATE = "indeterminate";
|
public static final String PROGRESS_BAR_INDETERMINATE = "indeterminate";
|
||||||
public static final String PROGRESS_BAR_MAX = "max";
|
|
||||||
public static final String PROGRESS_BAR_PROGRESS = "progress";
|
public static final String PROGRESS_BAR_PROGRESS = "progress";
|
||||||
public static final String ACTION_SET_PROGRESS_TEXT = "GB_Set_Progress_Text";
|
public static final String ACTION_SET_PROGRESS_TEXT = "GB_Set_Progress_Text";
|
||||||
public static final String ACTION_SET_INFO_TEXT = "GB_Set_Info_Text";
|
public static final String ACTION_SET_INFO_TEXT = "GB_Set_Info_Text";
|
||||||
@@ -111,8 +110,6 @@ public class GB {
|
|||||||
|
|
||||||
private static boolean notificationChannelsCreated;
|
private static boolean notificationChannelsCreated;
|
||||||
|
|
||||||
private static final String TAG = "GB";
|
|
||||||
|
|
||||||
public static void createNotificationChannels(Context context) {
|
public static void createNotificationChannels(Context context) {
|
||||||
if (notificationChannelsCreated) return;
|
if (notificationChannelsCreated) return;
|
||||||
|
|
||||||
@@ -541,6 +538,10 @@ public class GB {
|
|||||||
return nb.build();
|
return nb.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use {@link nodomain.freeyourgadget.gadgetbridge.util.notifications.GBProgressNotification}
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public static void updateTransferNotification(CharSequence title, CharSequence text, boolean ongoing, int percentage, Context context) {
|
public static void updateTransferNotification(CharSequence title, CharSequence text, boolean ongoing, int percentage, Context context) {
|
||||||
if (percentage == 100) {
|
if (percentage == 100) {
|
||||||
removeNotification(NOTIFICATION_ID_TRANSFER, context);
|
removeNotification(NOTIFICATION_ID_TRANSFER, context);
|
||||||
|
|||||||
+238
@@ -0,0 +1,238 @@
|
|||||||
|
/* Copyright (C) 2025 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 <https://www.gnu.org/licenses/>. */
|
||||||
|
package nodomain.freeyourgadget.gadgetbridge.util.notifications
|
||||||
|
|
||||||
|
import android.app.Notification
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.BuildConfig
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.R
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.GB
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.PendingIntentUtils
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.util.Random
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper class to keep a transfer notification updated, with utility functions to handle throttling and progress.
|
||||||
|
* <p>
|
||||||
|
* It is assumed that a chunk is in the same unit as the total progress, and not included in it until the chunk finishes.
|
||||||
|
* At any point, the progress percentage of the notification corresponds to (totalProgress + chunkProgress) / totalSize.
|
||||||
|
*/
|
||||||
|
class GBProgressNotification(
|
||||||
|
private val context: Context,
|
||||||
|
private val channelId: String
|
||||||
|
) {
|
||||||
|
private val notificationId: Int = Random().nextInt(100000)
|
||||||
|
|
||||||
|
@StringRes
|
||||||
|
private var titleRes = 0
|
||||||
|
|
||||||
|
@StringRes
|
||||||
|
private var textRes = 0
|
||||||
|
|
||||||
|
private var visible = false
|
||||||
|
|
||||||
|
private var lastNotificationUpdateTs: Long = -1
|
||||||
|
|
||||||
|
private var totalProgress: Long = 0
|
||||||
|
private var totalSize: Long = 0
|
||||||
|
private var chunkProgress: Long = 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the transfer notification, resetting all progress and size to zero.
|
||||||
|
*/
|
||||||
|
fun start(
|
||||||
|
@StringRes titleRes: Int,
|
||||||
|
@StringRes textRes: Int
|
||||||
|
) {
|
||||||
|
this.titleRes = titleRes
|
||||||
|
this.textRes = textRes
|
||||||
|
this.chunkProgress = 0
|
||||||
|
this.totalProgress = 0
|
||||||
|
this.totalSize = 0
|
||||||
|
|
||||||
|
LOG.debug(
|
||||||
|
"Started notification id={}, title={}",
|
||||||
|
notificationId,
|
||||||
|
if (titleRes != 0) context.getString(titleRes) else "0"
|
||||||
|
)
|
||||||
|
|
||||||
|
refresh(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setChunkProgress(chunkProgress: Long) {
|
||||||
|
LOG.debug("setChunkProgress id={}: {}", notificationId, chunkProgress)
|
||||||
|
this.chunkProgress = chunkProgress
|
||||||
|
refresh(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setTotalProgress(totalProgress: Long) {
|
||||||
|
LOG.debug("setTotalProgress id={}: {}", notificationId, totalProgress)
|
||||||
|
this.chunkProgress = 0
|
||||||
|
this.totalProgress = totalProgress
|
||||||
|
refresh(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setTotalSize(totalSize: Long) {
|
||||||
|
LOG.debug("setTotalSize id={}: {}", notificationId, totalSize)
|
||||||
|
this.totalSize = totalSize
|
||||||
|
refresh(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun incrementTotalProgress(inc: Long) {
|
||||||
|
LOG.debug("incrementTotalProgress id={}: {}", notificationId, inc)
|
||||||
|
this.chunkProgress = 0
|
||||||
|
this.totalProgress += inc
|
||||||
|
refresh(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun incrementTotalSize(inc: Long) {
|
||||||
|
LOG.debug("incrementTotalSize id={}: {}", notificationId, inc)
|
||||||
|
this.totalSize += inc
|
||||||
|
refresh(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setProgress(chunkProgress: Long, totalProgress: Long) {
|
||||||
|
LOG.debug("setProgress id={}: {} {}", notificationId, chunkProgress, totalProgress)
|
||||||
|
this.chunkProgress = chunkProgress
|
||||||
|
this.totalProgress = totalProgress
|
||||||
|
refresh(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getProgressPercentage(): Int {
|
||||||
|
var percentage = 0f
|
||||||
|
|
||||||
|
if (totalSize != 0L) {
|
||||||
|
percentage = ((totalProgress * 100) / totalSize.toFloat())
|
||||||
|
|
||||||
|
if (chunkProgress != 0L) {
|
||||||
|
percentage += ((chunkProgress * 100) / totalSize.toFloat())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return percentage.roundToInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun finish() {
|
||||||
|
visible = false
|
||||||
|
|
||||||
|
LOG.debug("Finishing notification id={}", notificationId)
|
||||||
|
|
||||||
|
GB.removeNotification(notificationId, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun refresh(force: Boolean) {
|
||||||
|
val percentage = getProgressPercentage()
|
||||||
|
|
||||||
|
LOG.debug("Updating notification, percentage={}", percentage)
|
||||||
|
|
||||||
|
if (visible) {
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
if (now - lastNotificationUpdateTs < MIN_TIME_BETWEEN_UPDATES && !force) {
|
||||||
|
LOG.debug("Throttling notification update")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lastNotificationUpdateTs = now
|
||||||
|
}
|
||||||
|
|
||||||
|
visible = true
|
||||||
|
|
||||||
|
val title: CharSequence = if (titleRes != 0) context.getString(titleRes) else "Unknown transfer"
|
||||||
|
val text = if (textRes != 0) {
|
||||||
|
context.getString(textRes)
|
||||||
|
} else if (totalSize != 0L) {
|
||||||
|
context.getString(R.string.busy_task_progress_int, totalProgress + chunkProgress, totalSize)
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
|
||||||
|
update(
|
||||||
|
title = title,
|
||||||
|
text = text,
|
||||||
|
ongoing = true,
|
||||||
|
percentage = percentage
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun update(
|
||||||
|
title: CharSequence,
|
||||||
|
text: CharSequence,
|
||||||
|
ongoing: Boolean,
|
||||||
|
percentage: Int
|
||||||
|
) {
|
||||||
|
if (percentage >= 100) {
|
||||||
|
GB.removeNotification(notificationId, context)
|
||||||
|
} else {
|
||||||
|
val notification: Notification = createProgressNotification(
|
||||||
|
title, text, ongoing, percentage, channelId, context
|
||||||
|
)
|
||||||
|
GB.notify(notificationId, notification, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val LOG: Logger = LoggerFactory.getLogger(GBProgressNotification::class.java)
|
||||||
|
|
||||||
|
const val MIN_TIME_BETWEEN_UPDATES = 1000L
|
||||||
|
|
||||||
|
private fun createProgressNotification(
|
||||||
|
title: CharSequence?,
|
||||||
|
text: CharSequence?,
|
||||||
|
ongoing: Boolean,
|
||||||
|
percentage: Int,
|
||||||
|
channelId: String,
|
||||||
|
context: Context
|
||||||
|
): Notification {
|
||||||
|
val notificationIntent = Intent(context, ControlCenterv2::class.java)
|
||||||
|
notificationIntent.setPackage(BuildConfig.APPLICATION_ID)
|
||||||
|
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||||
|
val pendingIntent = PendingIntentUtils.getActivity(
|
||||||
|
context,
|
||||||
|
0,
|
||||||
|
notificationIntent,
|
||||||
|
0,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
|
||||||
|
val nb = NotificationCompat.Builder(context, channelId)
|
||||||
|
.setTicker(title ?: context.getString(R.string.app_name))
|
||||||
|
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||||
|
.setContentTitle(title ?: context.getString(R.string.app_name))
|
||||||
|
.setStyle(NotificationCompat.BigTextStyle().bigText(text))
|
||||||
|
.setContentText(text)
|
||||||
|
.setContentIntent(pendingIntent)
|
||||||
|
.setOnlyAlertOnce(percentage > 0 && percentage < 100)
|
||||||
|
.setOngoing(ongoing)
|
||||||
|
|
||||||
|
if (ongoing) {
|
||||||
|
nb.setProgress(100, percentage, percentage == 0)
|
||||||
|
nb.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||||
|
} else {
|
||||||
|
nb.setProgress(0, 0, false)
|
||||||
|
nb.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nb.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -747,6 +747,8 @@
|
|||||||
<string name="busy_task_fetch_weight_data">Fetching weight data</string>
|
<string name="busy_task_fetch_weight_data">Fetching weight data</string>
|
||||||
<string name="busy_task_fetch_training_data">Fetching last training data</string>
|
<string name="busy_task_fetch_training_data">Fetching last training data</string>
|
||||||
<string name="busy_task_fetch_training_data_finished">Training data received!</string>
|
<string name="busy_task_fetch_training_data_finished">Training data received!</string>
|
||||||
|
<string name="busy_task_processing_files">Processing files</string>
|
||||||
|
<string name="busy_task_progress_int">%1$d of %2$d</string>
|
||||||
<string name="busy_task_busy">Busy…</string>
|
<string name="busy_task_busy">Busy…</string>
|
||||||
<string name="busy_task_configuring">Configuring</string>
|
<string name="busy_task_configuring">Configuring</string>
|
||||||
<string name="busy_task_downloading">Downloading</string>
|
<string name="busy_task_downloading">Downloading</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user