mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
Garmin / Xiaomi-protobuf: Move activity reprocessing to device settings
This commit is contained in:
+1
@@ -269,6 +269,7 @@ public abstract class GarminCoordinator extends AbstractBLEDeviceCoordinator {
|
||||
|
||||
final List<Integer> developer = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
|
||||
developer.add(R.xml.devicesettings_import_activity_files);
|
||||
developer.add(R.xml.devicesettings_reprocess_activity_files);
|
||||
developer.add(R.xml.devicesettings_keep_activity_data_on_device);
|
||||
developer.add(R.xml.devicesettings_fetch_unknown_files);
|
||||
developer.add(R.xml.devicesettings_install_unsupported_files);
|
||||
|
||||
+83
-6
@@ -30,15 +30,16 @@ import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
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;
|
||||
@@ -46,6 +47,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.notifications.GBProgressNotification;
|
||||
|
||||
public class GarminSettingsCustomizer implements DeviceSpecificSettingsCustomizer {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(GarminSettingsCustomizer.class);
|
||||
@@ -68,6 +70,14 @@ public class GarminSettingsCustomizer implements DeviceSpecificSettingsCustomize
|
||||
});
|
||||
}
|
||||
|
||||
final Preference reprocessActivityPref = handler.findPreference("reprocess_activity_files");
|
||||
if (reprocessActivityPref != null) {
|
||||
reprocessActivityPref.setOnPreferenceClickListener(preference -> {
|
||||
parseAllFitFilesFromStorage(handler.getContext(), handler.getDevice());
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
final Preference prefImportActivityFiles = handler.findPreference("import_activity_files");
|
||||
if (prefImportActivityFiles != null) {
|
||||
final ActivityResultLauncher<String[]> activityFileChooser = handler.registerForActivityResult(
|
||||
@@ -135,7 +145,7 @@ public class GarminSettingsCustomizer implements DeviceSpecificSettingsCustomize
|
||||
|
||||
final String currentFolder = prefs.getString(GarminPreferences.PREF_GARMIN_AGPS_FOLDER, "");
|
||||
|
||||
final Preference prefFolder = handler.findPreference(GarminPreferences.PREF_GARMIN_AGPS_FOLDER);
|
||||
final Preference prefFolder = Objects.requireNonNull(handler.findPreference(GarminPreferences.PREF_GARMIN_AGPS_FOLDER));
|
||||
final ActivityResultLauncher<Uri> agpsFolderChooser = handler.registerForActivityResult(
|
||||
new ActivityResultContracts.OpenDocumentTree(),
|
||||
localUri -> {
|
||||
@@ -251,9 +261,7 @@ public class GarminSettingsCustomizer implements DeviceSpecificSettingsCustomize
|
||||
builder.setTitle(R.string.garmin_agps_local_file);
|
||||
|
||||
final AtomicInteger selectedIdx = new AtomicInteger(0);
|
||||
builder.setSingleChoiceItems(files, checkedItem, (dialog, which) -> {
|
||||
selectedIdx.set(which);
|
||||
});
|
||||
builder.setSingleChoiceItems(files, checkedItem, (dialog, which) -> selectedIdx.set(which));
|
||||
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
final String selectedFilename = selectedIdx.get() > 0 ? files[selectedIdx.get()] : null;
|
||||
prefs.getPreferences().edit()
|
||||
@@ -268,6 +276,9 @@ public class GarminSettingsCustomizer implements DeviceSpecificSettingsCustomize
|
||||
|
||||
private void updateAgpsStatus(final DeviceSpecificSettingsHandler handler, final Prefs prefs, final String url) {
|
||||
final Preference prefStatus = handler.findPreference(GarminPreferences.agpsStatus(url));
|
||||
if (prefStatus == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String filename = prefs.getString(GarminPreferences.agpsFilename(url), "");
|
||||
if (filename.isEmpty()) {
|
||||
@@ -303,7 +314,7 @@ public class GarminSettingsCustomizer implements DeviceSpecificSettingsCustomize
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
public static final Creator<GarminSettingsCustomizer> CREATOR = new Creator<GarminSettingsCustomizer>() {
|
||||
public static final Creator<GarminSettingsCustomizer> CREATOR = new Creator<>() {
|
||||
@Override
|
||||
public GarminSettingsCustomizer createFromParcel(final Parcel in) {
|
||||
return new GarminSettingsCustomizer();
|
||||
@@ -323,4 +334,70 @@ public class GarminSettingsCustomizer implements DeviceSpecificSettingsCustomize
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
}
|
||||
|
||||
private static final AtomicBoolean PARSING_FROM_STORAGE = new AtomicBoolean(false);
|
||||
|
||||
private static void parseAllFitFilesFromStorage(final Context context, final GBDevice device) {
|
||||
if (!PARSING_FROM_STORAGE.compareAndSet(false, true)) {
|
||||
GB.toast(context, "Already parsing!", Toast.LENGTH_LONG, GB.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.info("Parsing all fit files from storage");
|
||||
|
||||
final List<File> fitFiles;
|
||||
try {
|
||||
final File exportDir = device.getDeviceCoordinator().getWritableExportDirectory(device, true);
|
||||
|
||||
if (!exportDir.exists() || !exportDir.isDirectory()) {
|
||||
LOG.error("export directory {} not found", exportDir);
|
||||
GB.toast(context, "export directory " + exportDir + " not found", Toast.LENGTH_LONG, GB.ERROR);
|
||||
PARSING_FROM_STORAGE.set(false);
|
||||
return;
|
||||
}
|
||||
|
||||
fitFiles = FileUtils.listRecursive(exportDir, (dir, name) -> name.endsWith(".fit"));
|
||||
if (fitFiles.isEmpty()) {
|
||||
LOG.error("No fit files found in {}", exportDir);
|
||||
GB.toast(context, "No fit files found in " + exportDir, Toast.LENGTH_LONG, GB.ERROR);
|
||||
PARSING_FROM_STORAGE.set(false);
|
||||
return;
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
LOG.error("Failed to parse from storage", e);
|
||||
GB.toast(context, "Failed to parse from storage", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
PARSING_FROM_STORAGE.set(false);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.debug("Got {} fit files to parse", fitFiles.size());
|
||||
|
||||
GB.toast(context, "Check notification for progress", Toast.LENGTH_LONG, GB.INFO);
|
||||
|
||||
final GBProgressNotification transferNotification = new GBProgressNotification(context, GB.NOTIFICATION_CHANNEL_ID_TRANSFER);
|
||||
transferNotification.start(R.string.busy_task_processing_files, 0, fitFiles.size());
|
||||
|
||||
//try (DBHandler handler = GBApplication.acquireDB()) {
|
||||
// final DaoSession session = handler.getDaoSession();
|
||||
// final Device device = DBHelper.getDevice(gbDevice, session);
|
||||
// //getCoordinator().deleteAllActivityData(device, session);
|
||||
//} catch (final Exception e) {
|
||||
// GB.toast(context, "Error deleting activity data", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
//}
|
||||
|
||||
final FitAsyncProcessor fitAsyncProcessor = new FitAsyncProcessor(context, device);
|
||||
fitAsyncProcessor.process(fitFiles, new FitAsyncProcessor.Callback() {
|
||||
@Override
|
||||
public void onProgress(final int i) {
|
||||
transferNotification.setTotalProgress(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish() {
|
||||
PARSING_FROM_STORAGE.set(false);
|
||||
transferNotification.finish();
|
||||
GB.signalActivityDataFinish(device);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -535,6 +535,7 @@ public abstract class XiaomiCoordinator extends AbstractBLEDeviceCoordinator {
|
||||
//
|
||||
deviceSpecificSettings.addRootScreen(
|
||||
DeviceSpecificSettingsScreen.DEVELOPER,
|
||||
R.xml.devicesettings_reprocess_activity_files,
|
||||
R.xml.devicesettings_keep_activity_data_on_device
|
||||
);
|
||||
|
||||
|
||||
+131
-2
@@ -19,27 +19,55 @@ package nodomain.freeyourgadget.gadgetbridge.devices.xiaomi;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsUtils.hidePrefIfNoneVisible;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsUtils.populateOrHideListPreference;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.Parcel;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
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.huami.HuamiConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityFileId;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
public class XiaomiSettingsCustomizer implements DeviceSpecificSettingsCustomizer {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(XiaomiSettingsCustomizer.class);
|
||||
|
||||
@Override
|
||||
public void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customizeSettings(final DeviceSpecificSettingsHandler handler, final Prefs prefs, final String rootKey) {
|
||||
final Preference reprocessActivityPref = handler.findPreference("reprocess_activity_files");
|
||||
if (reprocessActivityPref != null) {
|
||||
reprocessActivityPref.setOnPreferenceClickListener(preference -> {
|
||||
parseAllActivityFilesFromStorage(handler.getContext(), handler.getDevice());
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
final Preference activityMonitoringPref = handler.findPreference(DeviceSettingsPreferenceConst.PREF_HEARTRATE_ACTIVITY_MONITORING);
|
||||
if (activityMonitoringPref != null) {
|
||||
activityMonitoringPref.setVisible(false);
|
||||
@@ -69,7 +97,7 @@ public class XiaomiSettingsCustomizer implements DeviceSpecificSettingsCustomize
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
public static final Creator<XiaomiSettingsCustomizer> CREATOR = new Creator<XiaomiSettingsCustomizer>() {
|
||||
public static final Creator<XiaomiSettingsCustomizer> CREATOR = new Creator<>() {
|
||||
@Override
|
||||
public XiaomiSettingsCustomizer createFromParcel(final Parcel in) {
|
||||
return new XiaomiSettingsCustomizer();
|
||||
@@ -87,6 +115,107 @@ public class XiaomiSettingsCustomizer implements DeviceSpecificSettingsCustomize
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(final Parcel dest, final int flags) {
|
||||
public void writeToParcel(@NonNull final Parcel dest, final int flags) {
|
||||
}
|
||||
|
||||
private static final AtomicBoolean PARSING_FROM_STORAGE = new AtomicBoolean(false);
|
||||
|
||||
private static void parseAllActivityFilesFromStorage(final Context context, final GBDevice device) {
|
||||
if (!PARSING_FROM_STORAGE.compareAndSet(false, true)) {
|
||||
GB.toast(context, "Already parsing!", Toast.LENGTH_LONG, GB.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.info("Parsing all activity files from storage");
|
||||
|
||||
final List<File> activityFiles;
|
||||
try {
|
||||
final File externalFilesDir = device.getDeviceCoordinator().getWritableExportDirectory(device, true);
|
||||
final File exportDir = new File(externalFilesDir, "rawFetchOperations");
|
||||
|
||||
if (!exportDir.exists() || !exportDir.isDirectory()) {
|
||||
LOG.error("export directory {} not found", exportDir);
|
||||
GB.toast(context, "export directory " + exportDir + " not found", Toast.LENGTH_LONG, GB.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
activityFiles = FileUtils.listRecursive(exportDir, (dir, name) -> name.endsWith(".bin"));
|
||||
if (activityFiles.isEmpty()) {
|
||||
LOG.error("No activity files found in {}", exportDir);
|
||||
GB.toast(context, "No activity files found in " + exportDir, Toast.LENGTH_LONG, GB.ERROR);
|
||||
return;
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
LOG.error("Failed to parse from storage", e);
|
||||
GB.toast(context, "Failed to parse from storage", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.debug("Will parse {} files", activityFiles.size());
|
||||
|
||||
GB.toast(context, "Check notification for progress", Toast.LENGTH_LONG, GB.INFO);
|
||||
GB.updateTransferNotification("Parsing activity files", "...", true, 0, context);
|
||||
final long[] lastNotificationUpdateTs = new long[]{System.currentTimeMillis()};
|
||||
|
||||
final Handler handler = new Handler(context.getMainLooper());
|
||||
new Thread(() -> {
|
||||
try {
|
||||
int[] i = new int[]{0};
|
||||
for (final File activityFile : activityFiles) {
|
||||
i[0]++;
|
||||
|
||||
LOG.debug("Parsing {}", activityFile);
|
||||
|
||||
final long now = System.currentTimeMillis();
|
||||
if (now - lastNotificationUpdateTs[0] > 1500L) {
|
||||
lastNotificationUpdateTs[0] = now;
|
||||
handler.post(() -> {
|
||||
GB.updateTransferNotification(
|
||||
"Parsing activity files", "File " + i[0] + " of " + activityFiles.size(),
|
||||
true,
|
||||
(i[0] * 100) / activityFiles.size(), context
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// The logic below just replicates XiaomiActivityFileFetcher
|
||||
|
||||
final byte[] data;
|
||||
try (InputStream in = new FileInputStream(activityFile)) {
|
||||
data = FileUtils.readAll(in, 999999);
|
||||
} catch (final IOException ioe) {
|
||||
LOG.error("Failed to read {}", activityFile, ioe);
|
||||
continue;
|
||||
}
|
||||
|
||||
final byte[] fileIdBytes = Arrays.copyOfRange(data, 0, 7);
|
||||
final XiaomiActivityFileId fileId = XiaomiActivityFileId.from(fileIdBytes);
|
||||
|
||||
final XiaomiActivityParser activityParser = XiaomiActivityParser.create(fileId);
|
||||
if (activityParser == null) {
|
||||
LOG.warn("Failed to find parser for {}", fileId);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
if (activityParser.parse(context, device, fileId, data)) {
|
||||
LOG.info("Successfully parsed {}", fileId);
|
||||
} else {
|
||||
LOG.warn("Failed to parse {}", fileId);
|
||||
}
|
||||
} catch (final Exception ex) {
|
||||
LOG.error("Exception while parsing {}", fileId, ex);
|
||||
}
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
LOG.error("Failed to parse from storage", e);
|
||||
}
|
||||
|
||||
handler.post(() -> {
|
||||
PARSING_FROM_STORAGE.set(false);
|
||||
GB.updateTransferNotification("", "", false, 100, context);
|
||||
GB.signalActivityDataFinish(device);
|
||||
});
|
||||
}, "XiaomiReprocessThread").start();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-66
@@ -1382,71 +1382,6 @@ public class GarminSupport extends AbstractBTLESingleDeviceSupport implements IC
|
||||
|
||||
@Override
|
||||
public void onTestNewFunction() {
|
||||
parseAllFitFilesFromStorage();
|
||||
|
||||
}
|
||||
|
||||
boolean parsingFitFilesFromStorage = false;
|
||||
|
||||
private void parseAllFitFilesFromStorage() {
|
||||
if (parsingFitFilesFromStorage) {
|
||||
GB.toast(getContext(), "Already parsing!", Toast.LENGTH_LONG, GB.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
parsingFitFilesFromStorage = true;
|
||||
|
||||
LOG.info("Parsing all fit files from storage");
|
||||
|
||||
final List<File> fitFiles;
|
||||
try {
|
||||
final File exportDir = getWritableExportDirectory();
|
||||
|
||||
if (!exportDir.exists() || !exportDir.isDirectory()) {
|
||||
LOG.error("export directory {} not found", exportDir);
|
||||
GB.toast(getContext(), "export directory " + exportDir + " not found", Toast.LENGTH_LONG, GB.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
fitFiles = FileUtils.listRecursive(exportDir, (dir, name) -> name.endsWith(".fit"));
|
||||
if (fitFiles.isEmpty()) {
|
||||
LOG.error("No fit files found in {}", exportDir);
|
||||
GB.toast(getContext(), "No fit files found in " + exportDir, Toast.LENGTH_LONG, GB.ERROR);
|
||||
return;
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
LOG.error("Failed to parse from storage", e);
|
||||
GB.toast(getContext(), "Failed to parse from storage", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.debug("Got {} fit files to parse", fitFiles.size());
|
||||
|
||||
GB.toast(getContext(), "Check notification for progress", Toast.LENGTH_LONG, GB.INFO);
|
||||
|
||||
transferNotification.start(R.string.busy_task_processing_files, 0, fitFiles.size());
|
||||
|
||||
//try (DBHandler handler = GBApplication.acquireDB()) {
|
||||
// final DaoSession session = handler.getDaoSession();
|
||||
// final Device device = DBHelper.getDevice(gbDevice, session);
|
||||
// //getCoordinator().deleteAllActivityData(device, session);
|
||||
//} catch (final Exception e) {
|
||||
// GB.toast(getContext(), "Error deleting activity data", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
//}
|
||||
|
||||
final FitAsyncProcessor fitAsyncProcessor = new FitAsyncProcessor(getContext(), getDevice());
|
||||
fitAsyncProcessor.process(fitFiles, new FitAsyncProcessor.Callback() {
|
||||
@Override
|
||||
public void onProgress(final int i) {
|
||||
transferNotification.setTotalProgress(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish() {
|
||||
parsingFitFilesFromStorage = false;
|
||||
transferNotification.finish();
|
||||
GB.signalActivityDataFinish(getDevice());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-116
@@ -22,8 +22,6 @@ import android.content.Context;
|
||||
import android.location.Location;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
@@ -31,17 +29,11 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
|
||||
@@ -61,8 +53,6 @@ import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WorldClock;
|
||||
import nodomain.freeyourgadget.gadgetbridge.proto.xiaomi.XiaomiProto;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityFileId;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.AbstractXiaomiService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.XiaomiCalendarService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.XiaomiDataUploadService;
|
||||
@@ -74,13 +64,11 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.Xiao
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.XiaomiSystemService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.XiaomiWatchfaceService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.services.XiaomiWeatherService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
public class XiaomiSupport extends AbstractDeviceSupport {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(XiaomiSupport.class);
|
||||
private static final AtomicLong THREAD_COUNTER = new AtomicLong(0L);
|
||||
|
||||
private final XiaomiAuthService authService = new XiaomiAuthService(this);
|
||||
private final XiaomiMusicService musicService = new XiaomiMusicService(this);
|
||||
@@ -248,7 +236,6 @@ public class XiaomiSupport extends AbstractDeviceSupport {
|
||||
@Override
|
||||
public void onTestNewFunction() {
|
||||
//sendCommand("test new function", 2, 29);
|
||||
parseAllActivityFilesFromStorage();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -451,109 +438,6 @@ public class XiaomiSupport extends AbstractDeviceSupport {
|
||||
return StringUtils.replaceEach(inputString, EMOJI_SOURCE, EMOJI_TARGET);
|
||||
}
|
||||
|
||||
boolean parsingActivityFilesFromStorage = false;
|
||||
|
||||
private void parseAllActivityFilesFromStorage() {
|
||||
if (parsingActivityFilesFromStorage) {
|
||||
GB.toast(getContext(), "Already parsing!", Toast.LENGTH_LONG, GB.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
parsingActivityFilesFromStorage = true;
|
||||
|
||||
LOG.info("Parsing all activity files from storage");
|
||||
|
||||
final List<File> activityFiles;
|
||||
try {
|
||||
final File externalFilesDir = getCoordinator().getWritableExportDirectory(getDevice(), true);
|
||||
final File exportDir = new File(externalFilesDir, "rawFetchOperations");
|
||||
|
||||
if (!exportDir.exists() || !exportDir.isDirectory()) {
|
||||
LOG.error("export directory {} not found", exportDir);
|
||||
GB.toast(getContext(), "export directory " + exportDir + " not found", Toast.LENGTH_LONG, GB.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
activityFiles = FileUtils.listRecursive(exportDir, (dir, name) -> name.endsWith(".bin"));
|
||||
if (activityFiles.isEmpty()) {
|
||||
LOG.error("No activity files found in {}", exportDir);
|
||||
GB.toast(getContext(), "No activity files found in " + exportDir, Toast.LENGTH_LONG, GB.ERROR);
|
||||
return;
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
LOG.error("Failed to parse from storage", e);
|
||||
GB.toast(getContext(), "Failed to parse from storage", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.debug("Will parse {} files", activityFiles.size());
|
||||
|
||||
GB.toast(getContext(), "Check notification for progress", Toast.LENGTH_LONG, GB.INFO);
|
||||
GB.updateTransferNotification("Parsing activity files", "...", true, 0, getContext());
|
||||
final long[] lastNotificationUpdateTs = new long[]{System.currentTimeMillis()};
|
||||
|
||||
final Handler handler = new Handler(getContext().getMainLooper());
|
||||
new Thread(() -> {
|
||||
try {
|
||||
int[] i = new int[]{0};
|
||||
for (final File activityFile : activityFiles) {
|
||||
i[0]++;
|
||||
|
||||
LOG.debug("Parsing {}", activityFile);
|
||||
|
||||
final long now = System.currentTimeMillis();
|
||||
if (now - lastNotificationUpdateTs[0] > 1500L) {
|
||||
lastNotificationUpdateTs[0] = now;
|
||||
handler.post(() -> {
|
||||
GB.updateTransferNotification(
|
||||
"Parsing activity files", "File " + i[0] + " of " + activityFiles.size(),
|
||||
true,
|
||||
(i[0] * 100) / activityFiles.size(), getContext()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// The logic below just replicates XiaomiActivityFileFetcher
|
||||
|
||||
final byte[] data;
|
||||
try (InputStream in = new FileInputStream(activityFile)) {
|
||||
data = FileUtils.readAll(in, 999999);
|
||||
} catch (final IOException ioe) {
|
||||
LOG.error("Failed to read {}", activityFile, ioe);
|
||||
continue;
|
||||
}
|
||||
|
||||
final byte[] fileIdBytes = Arrays.copyOfRange(data, 0, 7);
|
||||
final XiaomiActivityFileId fileId = XiaomiActivityFileId.from(fileIdBytes);
|
||||
|
||||
final XiaomiActivityParser activityParser = XiaomiActivityParser.create(fileId);
|
||||
if (activityParser == null) {
|
||||
LOG.warn("Failed to find parser for {}", fileId);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
if (activityParser.parse(this, fileId, data)) {
|
||||
LOG.info("Successfully parsed {}", fileId);
|
||||
} else {
|
||||
LOG.warn("Failed to parse {}", fileId);
|
||||
}
|
||||
} catch (final Exception ex) {
|
||||
LOG.error("Exception while parsing {}", fileId, ex);
|
||||
}
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
LOG.error("Failed to parse from storage", e);
|
||||
}
|
||||
|
||||
handler.post(() -> {
|
||||
parsingActivityFilesFromStorage = false;
|
||||
GB.updateTransferNotification("", "", false, 100, getContext());
|
||||
GB.signalActivityDataFinish(getDevice());
|
||||
});
|
||||
}, "XiaomiSupport_" + THREAD_COUNTER.getAndIncrement()).start();
|
||||
}
|
||||
|
||||
public void setFeatureSupported(final String featureKey, final boolean supported) {
|
||||
LOG.debug("Setting feature {} -> {}", featureKey, supported ? "supported" : "not supported");
|
||||
evaluateGBDeviceEvent(new GBDeviceEventUpdatePreferences(featureKey, supported));
|
||||
|
||||
+3
-4
@@ -144,7 +144,7 @@ public class XiaomiActivityFileFetcher {
|
||||
}
|
||||
|
||||
try {
|
||||
if (activityParser.parse(mHealthService.getSupport(), fileId, data)) {
|
||||
if (activityParser.parse(mHealthService.getSupport().getContext(), mHealthService.getSupport().getDevice(), fileId, data)) {
|
||||
LOG.info("Successfully parsed {}", fileId);
|
||||
} else {
|
||||
LOG.warn("Failed to parse {}", fileId);
|
||||
@@ -202,9 +202,8 @@ public class XiaomiActivityFileFetcher {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static File getRawFile(final XiaomiSupport support, final XiaomiActivityFileId fileId) {
|
||||
public static File getRawFile(final GBDevice device, final XiaomiActivityFileId fileId) {
|
||||
try {
|
||||
final GBDevice device = support.getDevice();
|
||||
final File exportDirectory = device.getDeviceCoordinator().getWritableExportDirectory(device, true);
|
||||
final File targetDir = new File(exportDirectory, "rawFetchOperations");
|
||||
final File outputFile = fileId.getOutputFile(targetDir);
|
||||
@@ -220,7 +219,7 @@ public class XiaomiActivityFileFetcher {
|
||||
|
||||
protected void dumpBytesToExternalStorage(final XiaomiActivityFileId fileId, final byte[] bytes) {
|
||||
try {
|
||||
final File outputFile = getRawFile(mHealthService.getSupport(), fileId);
|
||||
final File outputFile = getRawFile(mHealthService.getSupport().getDevice(), fileId);
|
||||
final File parentFile = outputFile.getParentFile();
|
||||
if (parentFile != null) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
|
||||
+4
-2
@@ -16,6 +16,8 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@@ -29,8 +31,8 @@ import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl.ManualSamplesParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl.DailyDetailsParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl.DailySummaryParser;
|
||||
@@ -42,7 +44,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl
|
||||
public abstract class XiaomiActivityParser {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(XiaomiActivityParser.class);
|
||||
|
||||
public abstract boolean parse(final XiaomiSupport support, final XiaomiActivityFileId fileId, final byte[] bytes);
|
||||
public abstract boolean parse(final Context context, GBDevice device, final XiaomiActivityFileId fileId, final byte[] bytes);
|
||||
|
||||
protected BaseActivitySummary findOrCreateBaseActivitySummary(final DaoSession session,
|
||||
final Device device,
|
||||
|
||||
+3
-4
@@ -16,6 +16,7 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl;
|
||||
|
||||
import android.content.Context;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@@ -38,7 +39,6 @@ import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityFileId;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
@@ -47,7 +47,7 @@ public class DailyDetailsParser extends XiaomiActivityParser {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DailyDetailsParser.class);
|
||||
|
||||
@Override
|
||||
public boolean parse(final XiaomiSupport support, final XiaomiActivityFileId fileId, final byte[] bytes) {
|
||||
public boolean parse(final Context context, final GBDevice gbDevice, final XiaomiActivityFileId fileId, final byte[] bytes) {
|
||||
final int version = fileId.getVersion();
|
||||
final int headerSize;
|
||||
switch (version) {
|
||||
@@ -182,7 +182,6 @@ public class DailyDetailsParser extends XiaomiActivityParser {
|
||||
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||
final DaoSession session = handler.getDaoSession();
|
||||
|
||||
final GBDevice gbDevice = support.getDevice();
|
||||
final DeviceCoordinator coordinator = gbDevice.getDeviceCoordinator();
|
||||
final SampleProvider<XiaomiActivitySample> sampleProvider = (SampleProvider<XiaomiActivitySample>) coordinator.getSampleProvider(gbDevice, session);
|
||||
final Device device = DBHelper.getDevice(gbDevice, session);
|
||||
@@ -197,7 +196,7 @@ public class DailyDetailsParser extends XiaomiActivityParser {
|
||||
|
||||
return true;
|
||||
} catch (final Exception e) {
|
||||
GB.toast(support.getContext(), "Error saving activity samples", Toast.LENGTH_LONG, GB.ERROR);
|
||||
GB.toast(context, "Error saving activity samples", Toast.LENGTH_LONG, GB.ERROR);
|
||||
LOG.error("Error saving activity samples", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
+3
-4
@@ -16,6 +16,7 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl;
|
||||
|
||||
import android.content.Context;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@@ -31,7 +32,6 @@ import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.XiaomiDailySummarySam
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiDailySummarySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityFileId;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
@@ -40,7 +40,7 @@ public class DailySummaryParser extends XiaomiActivityParser {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DailySummaryParser.class);
|
||||
|
||||
@Override
|
||||
public boolean parse(final XiaomiSupport support, final XiaomiActivityFileId fileId, final byte[] bytes) {
|
||||
public boolean parse(final Context context, final GBDevice device, final XiaomiActivityFileId fileId, final byte[] bytes) {
|
||||
final int version = fileId.getVersion();
|
||||
final int headerSize;
|
||||
switch (version) {
|
||||
@@ -113,7 +113,6 @@ public class DailySummaryParser extends XiaomiActivityParser {
|
||||
|
||||
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||
final DaoSession session = handler.getDaoSession();
|
||||
final GBDevice device = support.getDevice();
|
||||
|
||||
sample.setDevice(DBHelper.getDevice(device, session));
|
||||
sample.setUser(DBHelper.getUser(session));
|
||||
@@ -121,7 +120,7 @@ public class DailySummaryParser extends XiaomiActivityParser {
|
||||
final XiaomiDailySummarySampleProvider sampleProvider = new XiaomiDailySummarySampleProvider(device, session);
|
||||
sampleProvider.addSample(sample);
|
||||
} catch (final Exception e) {
|
||||
GB.toast(support.getContext(), "Error saving daily summary", Toast.LENGTH_LONG, GB.ERROR);
|
||||
GB.toast(context, "Error saving daily summary", Toast.LENGTH_LONG, GB.ERROR);
|
||||
LOG.error("Error saving daily summary", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
+3
-4
@@ -16,6 +16,7 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl;
|
||||
|
||||
import android.content.Context;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@@ -35,7 +36,6 @@ import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiManualSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityFileId;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
@@ -44,7 +44,7 @@ public class ManualSamplesParser extends XiaomiActivityParser {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ManualSamplesParser.class);
|
||||
|
||||
@Override
|
||||
public boolean parse(final XiaomiSupport support, final XiaomiActivityFileId fileId, final byte[] bytes) {
|
||||
public boolean parse(final Context context, final GBDevice gbDevice, final XiaomiActivityFileId fileId, final byte[] bytes) {
|
||||
if (fileId.getVersion() != 2) {
|
||||
LOG.warn("Unknown manual samples version {}", fileId.getVersion());
|
||||
return false;
|
||||
@@ -110,7 +110,6 @@ public class ManualSamplesParser extends XiaomiActivityParser {
|
||||
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||
final DaoSession session = handler.getDaoSession();
|
||||
|
||||
final GBDevice gbDevice = support.getDevice();
|
||||
final Device device = DBHelper.getDevice(gbDevice, session);
|
||||
final User user = DBHelper.getUser(session);
|
||||
|
||||
@@ -122,7 +121,7 @@ public class ManualSamplesParser extends XiaomiActivityParser {
|
||||
final XiaomiManualSampleProvider sampleProvider = new XiaomiManualSampleProvider(gbDevice, session);
|
||||
sampleProvider.addSamples(samples);
|
||||
} catch (final Exception e) {
|
||||
GB.toast(support.getContext(), "Error saving manual samples", Toast.LENGTH_LONG, GB.ERROR);
|
||||
GB.toast(context, "Error saving manual samples", Toast.LENGTH_LONG, GB.ERROR);
|
||||
LOG.error("Error saving manual samples", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
+5
-8
@@ -16,6 +16,7 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl;
|
||||
|
||||
import android.content.Context;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@@ -40,7 +41,6 @@ import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiSleepStageSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiSleepTimeSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityFileId;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
@@ -49,7 +49,7 @@ public class SleepDetailsParser extends XiaomiActivityParser {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SleepDetailsParser.class);
|
||||
|
||||
@Override
|
||||
public boolean parse(final XiaomiSupport support, final XiaomiActivityFileId fileId, final byte[] bytes) {
|
||||
public boolean parse(final Context context, final GBDevice gbDevice, final XiaomiActivityFileId fileId, final byte[] bytes) {
|
||||
// Seems to come both as DetailType.DETAILS (version 2) and DetailType.SUMMARY (version 4, 5)
|
||||
final int version = fileId.getVersion();
|
||||
final int headerSize;
|
||||
@@ -314,7 +314,6 @@ public class SleepDetailsParser extends XiaomiActivityParser {
|
||||
// save all the samples that we got
|
||||
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||
final DaoSession session = handler.getDaoSession();
|
||||
final GBDevice gbDevice = support.getDevice();
|
||||
|
||||
final XiaomiSleepTimeSampleProvider sampleProvider = new XiaomiSleepTimeSampleProvider(gbDevice, session);
|
||||
|
||||
@@ -337,7 +336,7 @@ public class SleepDetailsParser extends XiaomiActivityParser {
|
||||
sampleProvider.addSample(summary);
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
GB.toast(support.getContext(), "Error saving sleep sample", Toast.LENGTH_LONG, GB.ERROR);
|
||||
GB.toast(context, "Error saving sleep sample", Toast.LENGTH_LONG, GB.ERROR);
|
||||
LOG.error("Error saving sleep sample", e);
|
||||
persistSuccess = false;
|
||||
}
|
||||
@@ -348,7 +347,6 @@ public class SleepDetailsParser extends XiaomiActivityParser {
|
||||
// Save the sleep stage samples
|
||||
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||
final DaoSession session = handler.getDaoSession();
|
||||
final GBDevice gbDevice = support.getDevice();
|
||||
final Device device = DBHelper.getDevice(gbDevice, session);
|
||||
final User user = DBHelper.getUser(session);
|
||||
|
||||
@@ -361,7 +359,7 @@ public class SleepDetailsParser extends XiaomiActivityParser {
|
||||
|
||||
sampleProvider.addSamples(stages);
|
||||
} catch (final Exception e) {
|
||||
GB.toast(support.getContext(), "Error saving sleep stage samples", Toast.LENGTH_LONG, GB.ERROR);
|
||||
GB.toast(context, "Error saving sleep stage samples", Toast.LENGTH_LONG, GB.ERROR);
|
||||
LOG.error("Error saving sleep stage samples", e);
|
||||
persistSuccess = false;
|
||||
}
|
||||
@@ -370,7 +368,6 @@ public class SleepDetailsParser extends XiaomiActivityParser {
|
||||
// Save the heart pulse samples
|
||||
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||
final DaoSession session = handler.getDaoSession();
|
||||
final GBDevice gbDevice = support.getDevice();
|
||||
final Device device = DBHelper.getDevice(gbDevice, session);
|
||||
final User user = DBHelper.getUser(session);
|
||||
|
||||
@@ -383,7 +380,7 @@ public class SleepDetailsParser extends XiaomiActivityParser {
|
||||
|
||||
sampleProvider.addSamples(heartPulseSamples);
|
||||
} catch (final Exception e) {
|
||||
GB.toast(support.getContext(), "Error saving heart pulse samples", Toast.LENGTH_LONG, GB.ERROR);
|
||||
GB.toast(context, "Error saving heart pulse samples", Toast.LENGTH_LONG, GB.ERROR);
|
||||
LOG.error("Error saving heart pulse samples", e);
|
||||
persistSuccess = false;
|
||||
}
|
||||
|
||||
+4
-6
@@ -16,6 +16,7 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl;
|
||||
|
||||
import android.content.Context;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@@ -37,7 +38,6 @@ import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiSleepStageSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.XiaomiSleepTimeSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityFileId;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
@@ -46,7 +46,7 @@ public class SleepStagesParser extends XiaomiActivityParser {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SleepStagesParser.class);
|
||||
|
||||
@Override
|
||||
public boolean parse(final XiaomiSupport support, final XiaomiActivityFileId fileId, final byte[] bytes) {
|
||||
public boolean parse(final Context context, final GBDevice gbDevice, final XiaomiActivityFileId fileId, final byte[] bytes) {
|
||||
if (fileId.getVersion() != 2) {
|
||||
LOG.warn("Unknown sleep stages version {}", fileId.getVersion());
|
||||
return false;
|
||||
@@ -124,7 +124,6 @@ public class SleepStagesParser extends XiaomiActivityParser {
|
||||
// Save the sleep time sample
|
||||
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||
final DaoSession session = handler.getDaoSession();
|
||||
final GBDevice gbDevice = support.getDevice();
|
||||
|
||||
sample.setDevice(DBHelper.getDevice(gbDevice, session));
|
||||
sample.setUser(DBHelper.getUser(session));
|
||||
@@ -145,7 +144,7 @@ public class SleepStagesParser extends XiaomiActivityParser {
|
||||
|
||||
sampleProvider.addSample(sample);
|
||||
} catch (final Exception e) {
|
||||
GB.toast(support.getContext(), "Error saving sleep sample", Toast.LENGTH_LONG, GB.ERROR);
|
||||
GB.toast(context, "Error saving sleep sample", Toast.LENGTH_LONG, GB.ERROR);
|
||||
LOG.error("Error saving sleep sample", e);
|
||||
return false;
|
||||
}
|
||||
@@ -153,7 +152,6 @@ public class SleepStagesParser extends XiaomiActivityParser {
|
||||
// Save the sleep stage samples
|
||||
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||
final DaoSession session = handler.getDaoSession();
|
||||
final GBDevice gbDevice = support.getDevice();
|
||||
final Device device = DBHelper.getDevice(gbDevice, session);
|
||||
final User user = DBHelper.getUser(session);
|
||||
|
||||
@@ -166,7 +164,7 @@ public class SleepStagesParser extends XiaomiActivityParser {
|
||||
|
||||
sampleProvider.addSamples(stages);
|
||||
} catch (final Exception e) {
|
||||
GB.toast(support.getContext(), "Error saving sleep stage samples", Toast.LENGTH_LONG, GB.ERROR);
|
||||
GB.toast(context, "Error saving sleep stage samples", Toast.LENGTH_LONG, GB.ERROR);
|
||||
LOG.error("Error saving sleep stage samples", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
+8
-7
@@ -16,6 +16,7 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl;
|
||||
|
||||
import android.content.Context;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@@ -35,11 +36,11 @@ import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.export.ActivityTrackExporter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.export.GPXExporter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityFileFetcher;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityFileId;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityParser;
|
||||
@@ -51,7 +52,7 @@ public class WorkoutGpsParser extends XiaomiActivityParser {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WorkoutGpsParser.class);
|
||||
|
||||
@Override
|
||||
public boolean parse(final XiaomiSupport support, final XiaomiActivityFileId fileId, final byte[] bytes) {
|
||||
public boolean parse(final Context context, final GBDevice gbDevice, final XiaomiActivityFileId fileId, final byte[] bytes) {
|
||||
final int version = fileId.getVersion();
|
||||
final int headerSize;
|
||||
final int sampleSize;
|
||||
@@ -119,7 +120,7 @@ public class WorkoutGpsParser extends XiaomiActivityParser {
|
||||
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
final DaoSession session = dbHandler.getDaoSession();
|
||||
final Device device = DBHelper.getDevice(support.getDevice(), session);
|
||||
final Device device = DBHelper.getDevice(gbDevice, session);
|
||||
final User user = DBHelper.getUser(session);
|
||||
|
||||
// Find the matching summary
|
||||
@@ -128,10 +129,10 @@ public class WorkoutGpsParser extends XiaomiActivityParser {
|
||||
// Set the info on the activity track
|
||||
activityTrack.setUser(user);
|
||||
activityTrack.setDevice(device);
|
||||
activityTrack.setName(ActivityKind.fromCode(summary.getActivityKind()).getLabel(support.getContext()));
|
||||
activityTrack.setName(ActivityKind.fromCode(summary.getActivityKind()).getLabel(context));
|
||||
|
||||
// The file was already persisted by XiaomiActivityFileFetcher - just use the existing file
|
||||
final File rawBytesFile = XiaomiActivityFileFetcher.getRawFile(support, fileId);
|
||||
final File rawBytesFile = XiaomiActivityFileFetcher.getRawFile(gbDevice, fileId);
|
||||
|
||||
// Save the gpx file
|
||||
final GPXExporter exporter = new GPXExporter();
|
||||
@@ -144,7 +145,7 @@ public class WorkoutGpsParser extends XiaomiActivityParser {
|
||||
exporter.performExport(activityTrack, gpxTargetFile);
|
||||
} catch (final ActivityTrackExporter.GPXTrackEmptyException ex) {
|
||||
exportGpxSuccess = false;
|
||||
GB.toast(support.getContext(), "This activity does not contain GPX tracks.", Toast.LENGTH_LONG, GB.ERROR, ex);
|
||||
GB.toast(context, "This activity does not contain GPX tracks.", Toast.LENGTH_LONG, GB.ERROR, ex);
|
||||
}
|
||||
|
||||
if (exportGpxSuccess) {
|
||||
@@ -155,7 +156,7 @@ public class WorkoutGpsParser extends XiaomiActivityParser {
|
||||
}
|
||||
session.getBaseActivitySummaryDao().insertOrReplace(summary);
|
||||
} catch (final Exception e) {
|
||||
GB.toast(support.getContext(), "Error saving workout gps", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
GB.toast(context, "Error saving workout gps", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+6
-5
@@ -77,6 +77,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.WORKOUT_LOAD;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl.XiaomiSimpleActivityParser.XIAOMI_WORKOUT_TYPE;
|
||||
|
||||
import android.content.Context;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -94,10 +95,10 @@ import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityFileId;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
|
||||
@@ -107,7 +108,7 @@ public class WorkoutSummaryParser extends XiaomiActivityParser implements Activi
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WorkoutSummaryParser.class);
|
||||
|
||||
@Override
|
||||
public boolean parse(final XiaomiSupport support, final XiaomiActivityFileId fileId, final byte[] bytes) {
|
||||
public boolean parse(final Context context, final GBDevice gbDevice, final XiaomiActivityFileId fileId, final byte[] bytes) {
|
||||
BaseActivitySummary summary = new BaseActivitySummary();
|
||||
|
||||
summary.setStartTime(fileId.getTimestamp()); // due to a bug this has to be set
|
||||
@@ -119,7 +120,7 @@ public class WorkoutSummaryParser extends XiaomiActivityParser implements Activi
|
||||
summary = parseBinaryData(summary, true);
|
||||
} catch (final Exception e) {
|
||||
LOG.error("Failed to parse workout summary", e);
|
||||
GB.toast(support.getContext(), "Failed to parse workout summary", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
GB.toast(context, "Failed to parse workout summary", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -133,7 +134,7 @@ public class WorkoutSummaryParser extends XiaomiActivityParser implements Activi
|
||||
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
final DaoSession session = dbHandler.getDaoSession();
|
||||
final Device device = DBHelper.getDevice(support.getDevice(), session);
|
||||
final Device device = DBHelper.getDevice(gbDevice, session);
|
||||
final User user = DBHelper.getUser(session);
|
||||
|
||||
final BaseActivitySummary existingSummary = findOrCreateBaseActivitySummary(session, device, user, fileId);
|
||||
@@ -144,7 +145,7 @@ public class WorkoutSummaryParser extends XiaomiActivityParser implements Activi
|
||||
|
||||
session.getBaseActivitySummaryDao().insertOrReplace(existingSummary);
|
||||
} catch (final Exception e) {
|
||||
GB.toast(support.getContext(), "Error saving activity summary", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
GB.toast(context, "Error saving activity summary", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -3971,6 +3971,8 @@
|
||||
<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_reprocess_activity_files_title">Reprocess activity files</string>
|
||||
<string name="pref_reprocess_activity_files_summary">Reprocess all 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="pref_install_unsupported_files_title">Install unsupported files</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_refresh"
|
||||
android:key="reprocess_activity_files"
|
||||
android:summary="@string/pref_reprocess_activity_files_summary"
|
||||
android:title="@string/pref_reprocess_activity_files_title" />
|
||||
</androidx.preference.PreferenceScreen>
|
||||
+1
-1
@@ -19,6 +19,6 @@ public class XiaomiActivityParserTest extends TestBase {
|
||||
final XiaomiActivityFileId fileId = XiaomiActivityFileId.from(fileIdBytes);
|
||||
final XiaomiActivityParser parser = XiaomiActivityParser.create(fileId);
|
||||
|
||||
parser.parse(null, fileId, data);
|
||||
parser.parse(null, null, fileId, data);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user