From 838671e2cedd7c19ca98b3b2e442b38bb5e3bc26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20Mart=C3=ADnez?= Date: Thu, 3 Jun 2021 17:16:03 +0200 Subject: [PATCH] IntervalsFragment doesn't use TrackDataHub anymore. This improve intervals performance. Fixes #808. --- .../opentracks/EspressoDeleteTrackTest.java | 1 - .../util/AnnouncementUtilsTest.java | 12 +- .../viewmodels/IntervalStatisticsTest.java | 3 +- .../opentracks/TrackRecordedActivity.java | 2 +- .../opentracks/TrackRecordingActivity.java | 2 +- .../adapters/IntervalStatisticsAdapter.java | 4 - .../provider/ContentProviderUtils.java | 10 ++ .../fragments/IntervalsFragment.java | 119 ++++------------ .../tasks/AnnouncementPeriodicTask.java | 3 +- .../viewmodels/IntervalStatistics.java | 45 ++++-- .../viewmodels/IntervalStatisticsModel.java | 128 ++++++++++++------ 11 files changed, 166 insertions(+), 163 deletions(-) diff --git a/src/androidTest/java/de/dennisguse/opentracks/EspressoDeleteTrackTest.java b/src/androidTest/java/de/dennisguse/opentracks/EspressoDeleteTrackTest.java index 4efb59a93..a3456fb2c 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/EspressoDeleteTrackTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/EspressoDeleteTrackTest.java @@ -4,7 +4,6 @@ package de.dennisguse.opentracks; import android.app.ActivityManager; import android.app.Instrumentation; import android.content.Context; -import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; diff --git a/src/androidTest/java/de/dennisguse/opentracks/util/AnnouncementUtilsTest.java b/src/androidTest/java/de/dennisguse/opentracks/util/AnnouncementUtilsTest.java index 003f2d86f..d9b3ff178 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/util/AnnouncementUtilsTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/util/AnnouncementUtilsTest.java @@ -52,7 +52,8 @@ public class AnnouncementUtilsTest { stats.setTotalAltitudeGain(6000f); List trackPoints = TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 10).second; - IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, Distance.of(1000), Distance.of(0)); + IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.of(1000), Distance.of(0)); + intervalStatistics.addTrackPoints(trackPoints); IntervalStatistics.Interval lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1); // when @@ -88,7 +89,8 @@ public class AnnouncementUtilsTest { stats.setTotalAltitudeGain(6000f); List trackPoints = TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 10).second; - IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, Distance.of(1000)); + IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.of(1000), Distance.of(0)); + intervalStatistics.addTrackPoints(trackPoints); IntervalStatistics.Interval lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1); // when @@ -124,7 +126,8 @@ public class AnnouncementUtilsTest { stats.setTotalAltitudeGain(6000f); List trackPoints = TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 10).second; - IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, Distance.of(1000)); + IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.of(1000), Distance.of(0)); + intervalStatistics.addTrackPoints(trackPoints); IntervalStatistics.Interval lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1); // when @@ -160,7 +163,8 @@ public class AnnouncementUtilsTest { stats.setTotalAltitudeGain(6000f); List trackPoints = TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 10).second; - IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, Distance.of(1000)); + IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.of(1000), Distance.of(0)); + intervalStatistics.addTrackPoints(trackPoints); IntervalStatistics.Interval lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1); // when diff --git a/src/androidTest/java/de/dennisguse/opentracks/viewmodels/IntervalStatisticsTest.java b/src/androidTest/java/de/dennisguse/opentracks/viewmodels/IntervalStatisticsTest.java index a0d558a1e..c90b96a1c 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/viewmodels/IntervalStatisticsTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/viewmodels/IntervalStatisticsTest.java @@ -114,7 +114,8 @@ public class IntervalStatisticsTest { } private void whenAndThen(List trackPoints, TrackStatistics trackStatistics, float distanceInterval) { - IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, Distance.of(distanceInterval), Distance.of(0)); + IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.of(distanceInterval), Distance.of(0)); + intervalStatistics.addTrackPoints(trackPoints); List intervalList = intervalStatistics.getIntervalList(); Distance totalDistance = Distance.of(0); float totalTime = 0L; diff --git a/src/main/java/de/dennisguse/opentracks/TrackRecordedActivity.java b/src/main/java/de/dennisguse/opentracks/TrackRecordedActivity.java index 599e74481..f832e46d4 100644 --- a/src/main/java/de/dennisguse/opentracks/TrackRecordedActivity.java +++ b/src/main/java/de/dennisguse/opentracks/TrackRecordedActivity.java @@ -289,7 +289,7 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement case 0: return StatisticsRecordedFragment.newInstance(trackId); case 1: - return IntervalsFragment.newInstance(true); + return IntervalsFragment.newInstance(trackId, true); case 2: return ChartFragment.newInstance(false); case 3: diff --git a/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java b/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java index f9054622d..29faca1a8 100644 --- a/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java +++ b/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java @@ -360,7 +360,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc case 0: return StatisticsRecordingFragment.newInstance(); case 1: - return IntervalsFragment.newInstance(false); + return IntervalsFragment.newInstance(trackId, false); case 2: return ChartFragment.newInstance(false); case 3: diff --git a/src/main/java/de/dennisguse/opentracks/adapters/IntervalStatisticsAdapter.java b/src/main/java/de/dennisguse/opentracks/adapters/IntervalStatisticsAdapter.java index 9b528ee2d..fed2e69e2 100644 --- a/src/main/java/de/dennisguse/opentracks/adapters/IntervalStatisticsAdapter.java +++ b/src/main/java/de/dennisguse/opentracks/adapters/IntervalStatisticsAdapter.java @@ -72,10 +72,6 @@ public class IntervalStatisticsAdapter extends RecyclerView.Adapter swapData(List data, boolean metricUnits, boolean isReportSpeed) { - if (intervalList == data && this.metricUnits == metricUnits && this.isReportSpeed == isReportSpeed) { - return null; - } - this.metricUnits = metricUnits; this.isReportSpeed = isReportSpeed; intervalList = data; diff --git a/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java b/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java index 0b6e85578..693a2fa70 100644 --- a/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java +++ b/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java @@ -745,6 +745,16 @@ public class ContentProviderUtils { return new TrackPointIterator(this, trackId, startTrackPointId); } + public List getTrackPoints(final Track.Id trackId, final TrackPoint.Id startTrackPointId) { + List trackPoints = new ArrayList<>(); + try (TrackPointIterator trackPointIterator = getTrackPointLocationIterator(trackId, startTrackPointId)) { + while (trackPointIterator.hasNext()) { + trackPoints.add(trackPointIterator.next()); + } + } + return trackPoints; + } + @Deprecated private TrackPoint findTrackPointBy(String selection, String[] selectionArgs) { try (Cursor cursor = getTrackPointCursor(null, selection, selectionArgs, TrackPointsColumns._ID)) { diff --git a/src/main/java/de/dennisguse/opentracks/fragments/IntervalsFragment.java b/src/main/java/de/dennisguse/opentracks/fragments/IntervalsFragment.java index 8830cbdae..f9cefd294 100644 --- a/src/main/java/de/dennisguse/opentracks/fragments/IntervalsFragment.java +++ b/src/main/java/de/dennisguse/opentracks/fragments/IntervalsFragment.java @@ -12,7 +12,6 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentActivity; import androidx.lifecycle.LiveData; import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.LinearLayoutManager; @@ -20,15 +19,10 @@ import androidx.recyclerview.widget.LinearLayoutManager; import java.util.List; import de.dennisguse.opentracks.R; -import de.dennisguse.opentracks.TrackActivityDataHubInterface; import de.dennisguse.opentracks.adapters.IntervalStatisticsAdapter; -import de.dennisguse.opentracks.content.TrackDataHub; -import de.dennisguse.opentracks.content.TrackDataListener; -import de.dennisguse.opentracks.content.data.Speed; import de.dennisguse.opentracks.content.data.Track; -import de.dennisguse.opentracks.content.data.TrackPoint; +import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.databinding.IntervalListViewBinding; -import de.dennisguse.opentracks.stats.TrackStatistics; import de.dennisguse.opentracks.util.PreferencesUtils; import de.dennisguse.opentracks.viewmodels.IntervalStatistics; import de.dennisguse.opentracks.viewmodels.IntervalStatisticsModel; @@ -36,31 +30,31 @@ import de.dennisguse.opentracks.viewmodels.IntervalStatisticsModel; /** * A fragment to display the intervals from recorded track. */ -public class IntervalsFragment extends Fragment implements TrackDataListener { +public class IntervalsFragment extends Fragment { private static final String TAG = IntervalsFragment.class.getSimpleName(); private static final String FROM_TOP_TO_BOTTOM_KEY = "fromTopToBottom"; + private static final String TRACK_ID_KEY = "trackId"; private IntervalStatisticsModel viewModel; protected IntervalStatisticsAdapter.StackMode stackModeListView; private IntervalStatisticsModel.IntervalOption selectedInterval; + private Track.Id trackId; private boolean metricUnits; private IntervalStatisticsAdapter adapter; private ArrayAdapter spinnerAdapter; private SharedPreferences sharedPreferences; - private TrackDataHub trackDataHub; private boolean isReportSpeed; private IntervalListViewBinding viewBinding; protected final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> { if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key) || PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) { - metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, getContext()); - uploadIntervals(); + updateIntervals(PreferencesUtils.isMetricUnits(sharedPreferences, getContext()), selectedInterval); if (spinnerAdapter != null) { spinnerAdapter.notifyDataSetChanged(); } @@ -70,12 +64,14 @@ public class IntervalsFragment extends Fragment implements TrackDataListener { /** * Creates an instance of this class. * + * @param trackId track's id. * @param fromTopToBottom If true then the intervals are shown from top to bottom (the first interval on top). Otherwise the intervals are shown from bottom to top. * @return IntervalsFragment instance. */ - public static Fragment newInstance(boolean fromTopToBottom) { + public static Fragment newInstance(Track.Id trackId, boolean fromTopToBottom) { Bundle bundle = new Bundle(); bundle.putBoolean(FROM_TOP_TO_BOTTOM_KEY, fromTopToBottom); + bundle.putParcelable(TRACK_ID_KEY, trackId); IntervalsFragment intervalsFragment = new IntervalsFragment(); intervalsFragment.setArguments(bundle); return intervalsFragment; @@ -85,6 +81,7 @@ public class IntervalsFragment extends Fragment implements TrackDataListener { public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); stackModeListView = getArguments().getBoolean(FROM_TOP_TO_BOTTOM_KEY, true) ? IntervalStatisticsAdapter.StackMode.STACK_FROM_TOP : IntervalStatisticsAdapter.StackMode.STACK_FROM_BOTTOM; + trackId = getArguments().getParcelable(TRACK_ID_KEY); } @Override @@ -127,8 +124,7 @@ public class IntervalsFragment extends Fragment implements TrackDataListener { viewBinding.spinnerIntervals.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView adapterView, View view, int i, long l) { - selectedInterval = IntervalStatisticsModel.IntervalOption.values()[i]; - uploadIntervals(); + updateIntervals(metricUnits, IntervalStatisticsModel.IntervalOption.values()[i]); } @Override @@ -141,11 +137,15 @@ public class IntervalsFragment extends Fragment implements TrackDataListener { public void onResume() { super.onResume(); - resumeTrackDataHub(); - sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null); + ContentProviderUtils contentProviderUtils = new ContentProviderUtils(getContext()); + Track track = contentProviderUtils.getTrack(trackId); + if (track != null) { + isReportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, getContext(), track.getCategory()); + } + viewModel = new ViewModelProvider(getActivity()).get(IntervalStatisticsModel.class); loadIntervals(); } @@ -153,8 +153,8 @@ public class IntervalsFragment extends Fragment implements TrackDataListener { @Override public void onPause() { super.onPause(); - pauseTrackDataHub(); sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); + viewModel.onPause(); } @Override @@ -180,88 +180,17 @@ public class IntervalsFragment extends Fragment implements TrackDataListener { if (viewModel == null) { return; } - LiveData> liveData = viewModel.getIntervalStats(metricUnits, selectedInterval); + LiveData> liveData = viewModel.getIntervalStats(trackId, metricUnits, selectedInterval); liveData.observe(getActivity(), intervalList -> adapter.swapData(intervalList, metricUnits, isReportSpeed)); } - private synchronized void uploadIntervals() { - if (viewModel == null) { - return; - } - viewModel.upload(metricUnits, selectedInterval); - } + private synchronized void updateIntervals(boolean metricUnits, IntervalStatisticsModel.IntervalOption selectedInterval) { + boolean update = metricUnits != this.metricUnits || !selectedInterval.equals(this.selectedInterval); + this.metricUnits = metricUnits; + this.selectedInterval = selectedInterval; - /** - * Resumes the trackDataHub. - * Needs to be synchronized because trackDataHub can be accessed by multiple threads. - */ - private synchronized void resumeTrackDataHub() { - trackDataHub = ((TrackActivityDataHubInterface) getActivity()).getTrackDataHub(); - trackDataHub.registerTrackDataListener(this); - } - - /** - * Pauses the trackDataHub. - * Needs to be synchronized because trackDataHub can be accessed by multiple threads. - */ - private synchronized void pauseTrackDataHub() { - trackDataHub.unregisterTrackDataListener(this); - trackDataHub = null; - } - - @Override - public void onTrackUpdated(Track track) { - if (isResumed()) { - getActivity().runOnUiThread(() -> { - if (isResumed()) { - // Set category. - String category = track != null ? track.getCategory() : ""; - - // Set rate label. - isReportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, getContext(), category); - viewBinding.intervalRate.setText(isReportSpeed ? R.string.stats_speed : R.string.stats_pace); - } - }); - } - } - - @Override - public void clearTrackPoints() { - if (isResumed() && viewModel != null) { - viewModel.clear(); - } - } - - @Override - public void onSampledInTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics unused, Speed unused2, double unused3) { - if (isResumed() && viewModel != null) { - viewModel.add(trackPoint); - } - } - - @Override - public void onSampledOutTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics unused) { - if (isResumed() && viewModel != null) { - viewModel.add(trackPoint); - } - } - - @Override - public void onNewTrackPointsDone() { - if (isResumed() && viewModel != null) { - runOnUiThread(viewModel::onNewTrackPoints); - } - } - - /** - * Runs a runnable on the UI thread. - * - * @param runnable the runnable - */ - private void runOnUiThread(Runnable runnable) { - FragmentActivity fragmentActivity = getActivity(); - if (fragmentActivity != null) { - fragmentActivity.runOnUiThread(runnable); + if (update && viewModel != null) { + viewModel.update(trackId, this.metricUnits, this.selectedInterval); } } } diff --git a/src/main/java/de/dennisguse/opentracks/services/tasks/AnnouncementPeriodicTask.java b/src/main/java/de/dennisguse/opentracks/services/tasks/AnnouncementPeriodicTask.java index 722a5974c..315653077 100644 --- a/src/main/java/de/dennisguse/opentracks/services/tasks/AnnouncementPeriodicTask.java +++ b/src/main/java/de/dennisguse/opentracks/services/tasks/AnnouncementPeriodicTask.java @@ -189,7 +189,8 @@ public class AnnouncementPeriodicTask implements PeriodicTask { boolean isReportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, context, category); Distance minGPSDistance = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context); - IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, Distance.one(isMetricUnits), minGPSDistance); + IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.one(isMetricUnits), minGPSDistance); + intervalStatistics.addTrackPoints(trackPoints); IntervalStatistics.Interval lastInterval = intervalStatistics.getLastInterval(); String announcement = AnnouncementUtils.getAnnouncement(context, trackStatistics, isMetricUnits, isReportSpeed, lastInterval); diff --git a/src/main/java/de/dennisguse/opentracks/viewmodels/IntervalStatistics.java b/src/main/java/de/dennisguse/opentracks/viewmodels/IntervalStatistics.java index 4650a36c2..d3ff8716e 100644 --- a/src/main/java/de/dennisguse/opentracks/viewmodels/IntervalStatistics.java +++ b/src/main/java/de/dennisguse/opentracks/viewmodels/IntervalStatistics.java @@ -14,25 +14,32 @@ import de.dennisguse.opentracks.stats.TrackStatistics; import de.dennisguse.opentracks.stats.TrackStatisticsUpdater; public class IntervalStatistics { - private final List intervalList = new ArrayList<>(); + private TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater(); + private final List intervalList; private final Distance distanceInterval; + private final Distance minGPSDistance; + private Interval interval, lastInterval; /** - * @param trackPoints the list of TrackPoint. * @param distanceInterval distance of every interval. * @param minGPSDistance the setting value for GPS distance. */ - public IntervalStatistics(@NonNull List trackPoints, Distance distanceInterval, Distance minGPSDistance) { - intervalList.clear(); + public IntervalStatistics(Distance distanceInterval, Distance minGPSDistance) { this.distanceInterval = distanceInterval; + this.minGPSDistance = minGPSDistance; + interval = new Interval(); + lastInterval = new Interval(); + intervalList = new ArrayList<>(); + intervalList.add(lastInterval); + } + + public void addTrackPoints(@NonNull List trackPoints) { if (trackPoints.size() == 0) { return; } - TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater(); - Interval interval = new Interval(); - + boolean newIntervalAdded = false; for (TrackPoint trackPoint : trackPoints) { trackStatisticsUpdater.addTrackPoint(trackPoint, minGPSDistance); @@ -43,17 +50,24 @@ public class IntervalStatistics { Interval adjustedInterval = new Interval(interval); adjustedInterval.adjust(adjustFactor); + intervalList.remove(intervalList.size() - 1); intervalList.add(adjustedInterval); interval = new Interval(interval.distance.minus(adjustedInterval.distance), interval.time.minus(adjustedInterval.time)); trackStatisticsUpdater = new TrackStatisticsUpdater(); trackStatisticsUpdater.addTrackPoint(trackPoint, minGPSDistance); + + lastInterval = new Interval(interval); + intervalList.add(lastInterval); + + newIntervalAdded = true; } } - if (trackStatisticsUpdater.getTrackStatistics().getTotalDistance().toM() >= 1d) { - interval.update(trackStatisticsUpdater.getTrackStatistics(), null); - intervalList.add(interval); + if (newIntervalAdded) { + lastInterval.update(trackStatisticsUpdater.getTrackStatistics(), null); + } else { + lastInterval.set(trackStatisticsUpdater.getTrackStatistics()); } } @@ -110,7 +124,7 @@ public class IntervalStatistics { loss_m = i.loss_m; } - public void adjust(double adjustFactor) { + private void adjust(double adjustFactor) { distance = distance.multipliedBy(adjustFactor); time = Duration.ofMillis((long) (time.toMillis() * adjustFactor)); } @@ -131,7 +145,7 @@ public class IntervalStatistics { return loss_m; } - public void update(TrackStatistics trackStatistics, @Nullable TrackPoint lastTrackPoint) { + private void update(TrackStatistics trackStatistics, @Nullable TrackPoint lastTrackPoint) { distance = distance.plus(trackStatistics.getTotalDistance()); time = time.plus(trackStatistics.getTotalTime()); gain_m = trackStatistics.hasTotalAltitudeGain() ? trackStatistics.getTotalAltitudeGain() : gain_m; @@ -141,5 +155,12 @@ public class IntervalStatistics { loss_m = lastTrackPoint.hasAltitudeLoss() ? loss_m - lastTrackPoint.getAltitudeLoss() : loss_m; } } + + private void set(TrackStatistics trackStatistics) { + distance = trackStatistics.getTotalDistance(); + time = trackStatistics.getTotalTime(); + gain_m = trackStatistics.hasTotalAltitudeGain() ? trackStatistics.getTotalAltitudeGain() : gain_m; + loss_m = trackStatistics.hasTotalAltitudeLoss() ? trackStatistics.getTotalAltitudeLoss() : loss_m; + } } } diff --git a/src/main/java/de/dennisguse/opentracks/viewmodels/IntervalStatisticsModel.java b/src/main/java/de/dennisguse/opentracks/viewmodels/IntervalStatisticsModel.java index ce2d1f944..0c8e53f1f 100644 --- a/src/main/java/de/dennisguse/opentracks/viewmodels/IntervalStatisticsModel.java +++ b/src/main/java/de/dennisguse/opentracks/viewmodels/IntervalStatisticsModel.java @@ -1,19 +1,27 @@ package de.dennisguse.opentracks.viewmodels; import android.app.Application; +import android.content.ContentResolver; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.os.HandlerThread; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.MutableLiveData; -import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import de.dennisguse.opentracks.content.data.Distance; +import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.TrackPoint; +import de.dennisguse.opentracks.content.data.TrackPointsColumns; +import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.util.PreferencesUtils; -import de.dennisguse.opentracks.util.UnitConversions; /** * This model is used to load intervals for a track. @@ -21,65 +29,95 @@ import de.dennisguse.opentracks.util.UnitConversions; */ public class IntervalStatisticsModel extends AndroidViewModel { - private final List trackPoints = new ArrayList<>(); + private static final String TAG = IntervalStatisticsModel.class.getSimpleName(); + private MutableLiveData> intervalsLiveData; + private IntervalStatistics intervalStatistics; private Distance distanceInterval; - private Distance minGPSDistance; + private final Distance minGPSDistance; + private final ContentResolver contentResolver; + private ContentObserver trackPointsTableObserver; + private TrackPoint.Id lastTrackPointId; + + private final Executor executor = Executors.newSingleThreadExecutor(); + private HandlerThread handlerThread; + private Handler handler; public IntervalStatisticsModel(@NonNull Application application) { super(application); minGPSDistance = PreferencesUtils.getRecordingDistanceInterval(PreferencesUtils.getSharedPreferences(application), application); + contentResolver = getApplication().getContentResolver(); } - public MutableLiveData> getIntervalStats(boolean metricUnits, @Nullable IntervalOption interval) { - synchronized (trackPoints) { - if (intervalsLiveData == null) { - if (interval == null) { - interval = IntervalOption.OPTION_1; - } - - intervalsLiveData = new MutableLiveData<>(); - distanceInterval = interval.getDistance(metricUnits); - loadIntervalStatistics(); - } - return intervalsLiveData; + @Override + protected void onCleared() { + super.onCleared(); + if (trackPointsTableObserver != null) { + contentResolver.unregisterContentObserver(trackPointsTableObserver); } - } - - private void loadIntervalStatistics() { - IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, distanceInterval, minGPSDistance); - intervalsLiveData.postValue(intervalStatistics.getIntervalList()); - } - - public void add(TrackPoint trackPoint) { - synchronized (trackPoints) { - trackPoints.add(trackPoint); + if (handlerThread != null) { + handlerThread.getLooper().quit(); + handlerThread = null; } + handler = null; } - public void onNewTrackPoints() { - synchronized (trackPoints) { - if (intervalsLiveData != null) { - loadIntervalStatistics(); - } - } - } - - public void clear() { - synchronized (trackPoints) { - trackPoints.clear(); - } - } - - public void upload(boolean metricUnits, @Nullable IntervalOption interval) { - synchronized (trackPoints) { + public MutableLiveData> getIntervalStats(Track.Id trackId, boolean metricUnits, @Nullable IntervalOption interval) { + if (intervalsLiveData == null) { if (interval == null) { interval = IntervalOption.OPTION_1; } + intervalsLiveData = new MutableLiveData<>(); distanceInterval = interval.getDistance(metricUnits); - loadIntervalStatistics(); + intervalStatistics = new IntervalStatistics(distanceInterval, minGPSDistance); + + loadIntervalStatistics(trackId); } + + registerTrackPointsObserver(trackId); + + return intervalsLiveData; + } + + private void loadIntervalStatistics(Track.Id trackId) { + executor.execute(() -> { + ContentProviderUtils contentProviderUtils = new ContentProviderUtils(getApplication()); + List trackPoints = contentProviderUtils.getTrackPoints(trackId, lastTrackPointId); + lastTrackPointId = trackPoints.size() > 0 ? trackPoints.get(trackPoints.size() - 1).getId() : lastTrackPointId; + intervalStatistics.addTrackPoints(trackPoints); + intervalsLiveData.postValue(intervalStatistics.getIntervalList()); + }); + } + + private void registerTrackPointsObserver(Track.Id trackId) { + handlerThread = new HandlerThread(TAG); + handlerThread.start(); + handler = new Handler(handlerThread.getLooper()); + trackPointsTableObserver = new ContentObserver(handler) { + @Override + public void onChange(boolean selfChange, Uri uri) { + loadIntervalStatistics(trackId); + } + }; + contentResolver.registerContentObserver(TrackPointsColumns.CONTENT_URI_BY_TRACKID, false, trackPointsTableObserver); + } + + public void onPause() { + if (trackPointsTableObserver != null) { + contentResolver.unregisterContentObserver(trackPointsTableObserver); + } + } + + public void update(Track.Id trackId, boolean metricUnits, @Nullable IntervalOption interval) { + if (interval == null) { + interval = IntervalOption.OPTION_1; + } + + lastTrackPointId = null; + distanceInterval = interval.getDistance(metricUnits); + intervalStatistics = new IntervalStatistics(distanceInterval, minGPSDistance); + loadIntervalStatistics(trackId); } /** @@ -107,6 +145,10 @@ public class IntervalStatisticsModel extends AndroidViewModel { .multipliedBy(multiplier); } + public boolean equals(IntervalOption intervalOption) { + return intervalOption != null && this.multiplier == intervalOption.multiplier; + } + @Override public String toString() { return "" + multiplier; //TODO Somehow IntervalsFragment relies on a parsable Integer.