mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2025-03-12 09:10:11 +01:00
Garmin: Allow manual import of activity files
This commit is contained in:
parent
bbcd09dae1
commit
b81784f3b9
@ -93,6 +93,9 @@ public class ActivitySummariesGpsFragment extends AbstractGBFragment {
|
||||
|
||||
public static List<ActivityPoint> getActivityPoints(final File trackFile) {
|
||||
final List<ActivityPoint> points = new ArrayList<>();
|
||||
if (trackFile == null) {
|
||||
return points;
|
||||
}
|
||||
if (trackFile.getName().endsWith(".gpx")) {
|
||||
try (FileInputStream inputStream = new FileInputStream(trackFile)) {
|
||||
final GpxParser gpxParser = new GpxParser(inputStream);
|
||||
|
@ -223,6 +223,7 @@ public abstract class GarminCoordinator extends AbstractBLEDeviceCoordinator {
|
||||
connection.add(R.xml.devicesettings_high_mtu);
|
||||
|
||||
final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
|
||||
developer.add(R.xml.devicesettings_import_activity_files);
|
||||
developer.add(R.xml.devicesettings_keep_activity_data_on_device);
|
||||
developer.add(R.xml.devicesettings_fetch_unknown_files);
|
||||
|
||||
|
@ -22,7 +22,10 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@ -35,9 +38,12 @@ import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.agps.GarminAgpsStatus;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitAsyncProcessor;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
@ -62,6 +68,64 @@ public class GarminSettingsCustomizer implements DeviceSpecificSettingsCustomize
|
||||
});
|
||||
}
|
||||
|
||||
final Preference prefImportActivityFiles = handler.findPreference("import_activity_files");
|
||||
if (prefImportActivityFiles != null) {
|
||||
final ActivityResultLauncher<String[]> activityFileChooser = handler.registerForActivityResult(
|
||||
new ActivityResultContracts.OpenMultipleDocuments(),
|
||||
localUris -> {
|
||||
LOG.info("Files to import: {}", localUris);
|
||||
if (localUris != null) {
|
||||
final List<File> filesToProcess = new ArrayList<>(localUris.size());
|
||||
|
||||
final Context context = handler.getContext();
|
||||
for (final Uri uri : localUris) {
|
||||
final File file;
|
||||
try {
|
||||
file = File.createTempFile("activity-files-import", ".bin", context.getCacheDir());
|
||||
file.deleteOnExit();
|
||||
FileUtils.copyURItoFile(context, uri, file);
|
||||
filesToProcess.add(file);
|
||||
} catch (final IOException e) {
|
||||
LOG.error("Failed to create temp file for activity file", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (filesToProcess.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final FitAsyncProcessor fitAsyncProcessor = new FitAsyncProcessor(context, handler.getDevice());
|
||||
final long[] lastNotificationUpdateTs = new long[]{System.currentTimeMillis()};
|
||||
fitAsyncProcessor.process(filesToProcess, new FitAsyncProcessor.Callback() {
|
||||
@Override
|
||||
public void onProgress(final int i) {
|
||||
final long now = System.currentTimeMillis();
|
||||
if (now - lastNotificationUpdateTs[0] > 1500L) {
|
||||
lastNotificationUpdateTs[0] = now;
|
||||
GB.updateTransferNotification(
|
||||
"Parsing fit files", "File " + i + " of " + filesToProcess.size(),
|
||||
true,
|
||||
(i * 100) / filesToProcess.size(), context
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish() {
|
||||
GB.updateTransferNotification("", "", false, 100, context);
|
||||
GB.toast("Parsed " + filesToProcess.size() + " files", Toast.LENGTH_SHORT, GB.INFO);
|
||||
handler.getDevice().sendDeviceUpdateIntent(context);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
prefImportActivityFiles.setOnPreferenceClickListener(preference -> {
|
||||
activityFileChooser.launch(new String[]{"*/*"});
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
final PreferenceCategory prefAgpsHeader = handler.findPreference(DeviceSettingsPreferenceConst.PREF_HEADER_AGPS);
|
||||
if (prefAgpsHeader != null) {
|
||||
final List<String> urls = prefs.getList(GarminPreferences.PREF_AGPS_KNOWN_URLS, Collections.emptyList(), "\n");
|
||||
|
@ -8,9 +8,12 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.SortedMap;
|
||||
@ -78,6 +81,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitSport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitStressLevel;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitTimeInZone;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class FitImporter {
|
||||
@ -329,12 +333,41 @@ public class FitImporter {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the file is not yet on the export directory (eg. we're importing from phone storage), copy it
|
||||
File finalExportFile = file;
|
||||
try {
|
||||
final File exportDirectory = gbDevice.getDeviceCoordinator().getWritableExportDirectory(gbDevice);
|
||||
if (!file.getAbsolutePath().startsWith(exportDirectory.getAbsolutePath())) {
|
||||
final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.ROOT);
|
||||
final StringBuilder sb = new StringBuilder(fileId.getType().name());
|
||||
if (fileId.getTimeCreated() != null && fileId.getTimeCreated() != 0) {
|
||||
sb.append("_").append(SDF.format(new Date(fileId.getTimeCreated() * 1000L)));
|
||||
}
|
||||
sb.append(".fit");
|
||||
|
||||
final File exportFile = new File(exportDirectory, sb.toString());
|
||||
if (exportFile.isFile()) {
|
||||
// Prevent overwrite
|
||||
LOG.warn("Fit file {} already exists as {}", file, exportFile);
|
||||
} else {
|
||||
LOG.debug("Copying {} to {}", file, exportFile);
|
||||
|
||||
FileUtils.copyFile(file, exportFile);
|
||||
exportFile.setLastModified(file.lastModified());
|
||||
}
|
||||
|
||||
finalExportFile = exportFile;
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
LOG.error("Failed to copy file to export directory", e);
|
||||
}
|
||||
|
||||
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||
final DaoSession session = handler.getDaoSession();
|
||||
|
||||
switch (fileId.getType()) {
|
||||
case ACTIVITY:
|
||||
persistWorkout(file, session);
|
||||
persistWorkout(finalExportFile, session);
|
||||
break;
|
||||
case MONITOR:
|
||||
persistActivitySamples(session);
|
||||
|
@ -144,7 +144,6 @@ public class FileUtils {
|
||||
}
|
||||
try (InputStream fin = new BufferedInputStream(in)) {
|
||||
copyStreamToFile(fin, destFile);
|
||||
fin.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3505,6 +3505,8 @@
|
||||
<string name="battery_full_threshold">Full battery threshold</string>
|
||||
<string name="default_percentage">Default (%1$d%%)</string>
|
||||
<string name="battery_percentage_str">%1$s%%</string>
|
||||
<string name="pref_import_activity_files_title">Import activity files</string>
|
||||
<string name="pref_import_activity_files_summary">Manually import activity files from the phone storage</string>
|
||||
<string name="pref_fetch_unknown_files_title">Fetch unknown files</string>
|
||||
<string name="pref_fetch_unknown_files_summary">Fetch unknown activity files from the watch. They will not be processed, but will be saved in the phone.</string>
|
||||
<string name="cannot_upload_watchface_too_many_watchfaces_installed">"Cannot upload watchface, too many watchfaces installed"</string>
|
||||
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<Preference
|
||||
android:icon="@drawable/ic_file_upload"
|
||||
android:key="import_activity_files"
|
||||
android:summary="@string/pref_import_activity_files_summary"
|
||||
android:title="@string/pref_import_activity_files_title" />
|
||||
</androidx.preference.PreferenceScreen>
|
Loading…
x
Reference in New Issue
Block a user