From 679c1d7e3e326ec0400b850e291373d6c7612301 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n?= Date: Fri, 9 Oct 2020 18:03:19 +0200 Subject: [PATCH] Intervals: TrackDataHub instead of ContentProvider. Fixes #453. --- .../opentracks/TrackRecordedActivity.java | 2 +- .../opentracks/TrackRecordingActivity.java | 23 +- .../opentracks/content/TrackDataHub.java | 6 +- .../fragments/IntervalsFragment.java | 196 ++++++++++-------- .../viewmodels/IntervalStatisticsModel.java | 57 ++--- 5 files changed, 140 insertions(+), 144 deletions(-) diff --git a/src/main/java/de/dennisguse/opentracks/TrackRecordedActivity.java b/src/main/java/de/dennisguse/opentracks/TrackRecordedActivity.java index b6183a732..04929fd02 100644 --- a/src/main/java/de/dennisguse/opentracks/TrackRecordedActivity.java +++ b/src/main/java/de/dennisguse/opentracks/TrackRecordedActivity.java @@ -253,7 +253,7 @@ public class TrackRecordedActivity extends AbstractListActivity implements Choos case 0: return StatisticsRecordedFragment.newInstance(trackId); case 1: - return IntervalsFragment.newInstance(trackId); + return IntervalsFragment.newInstance(); 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 9421d7aa1..876461911 100644 --- a/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java +++ b/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java @@ -66,9 +66,6 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc // Preferences private boolean recordingTrackPaused; - // Intervals recording fragment needs Track.Id when the activity creates it and knowing when category change. - private OnTrackRecordingListener intervalsListener; - private final Runnable bindChangedCallback = new Runnable() { @Override public void run() { @@ -85,9 +82,6 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc if (trackId == null) { // trackId isn't initialized -> leads a new recording. trackId = service.startNewTrack(); - if (intervalsListener != null) { - intervalsListener.onTrackId(trackId); - } } else { // trackId is initialized -> resumes the track. service.resumeTrack(trackId); @@ -370,7 +364,6 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc Track track = contentProviderUtils.getTrack(trackId); String category = getString(TrackIconUtils.getIconActivityType(iconValue)); TrackUtils.updateTrack(this, track, null, category, null, contentProviderUtils); - intervalsListener.onCategoryChanged(category); } private class CustomFragmentPagerAdapter extends FragmentPagerAdapter { @@ -391,7 +384,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc case 0: return StatisticsRecordingFragment.newInstance(); case 1: - return IntervalsFragment.IntervalsRecordingFragment.newInstance(trackId); + return IntervalsFragment.IntervalsRecordingFragment.newInstance(); case 2: return ChartFragment.newInstance(false); case 3: @@ -417,16 +410,4 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc } } } - - public interface OnTrackRecordingListener { - void onTrackId(Track.Id trackId); - void onCategoryChanged(String category); - } - - public void setTrackIdListener(OnTrackRecordingListener listener) { - this.intervalsListener = listener; - if (trackId != null) { - listener.onTrackId(trackId); - } - } -} \ No newline at end of file +} diff --git a/src/main/java/de/dennisguse/opentracks/content/TrackDataHub.java b/src/main/java/de/dennisguse/opentracks/content/TrackDataHub.java index dd36070ba..28758833d 100644 --- a/src/main/java/de/dennisguse/opentracks/content/TrackDataHub.java +++ b/src/main/java/de/dennisguse/opentracks/content/TrackDataHub.java @@ -54,17 +54,15 @@ import de.dennisguse.opentracks.util.PreferencesUtils; public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeListener { /** - * Target number of track points displayed by the map overlay. + * Target number of track points displayed by the diagrams (recommended). * We may display more than this number of points. */ - @Deprecated private static final int TARGET_DISPLAYED_TRACKPOINTS = 5000; /** - * Maximum number of markers to displayed. + * Maximum number of markers to displayed in the diagrams. */ @VisibleForTesting - @Deprecated private static final int MAX_DISPLAYED_MARKERS = 128; private static final String TAG = TrackDataHub.class.getSimpleName(); diff --git a/src/main/java/de/dennisguse/opentracks/fragments/IntervalsFragment.java b/src/main/java/de/dennisguse/opentracks/fragments/IntervalsFragment.java index 503ce26a6..d7663831f 100644 --- a/src/main/java/de/dennisguse/opentracks/fragments/IntervalsFragment.java +++ b/src/main/java/de/dennisguse/opentracks/fragments/IntervalsFragment.java @@ -15,14 +15,16 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; -import androidx.lifecycle.LiveData; -import androidx.lifecycle.ViewModelProvider; +import androidx.fragment.app.FragmentActivity; import de.dennisguse.opentracks.R; -import de.dennisguse.opentracks.TrackRecordingActivity; +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.Marker; import de.dennisguse.opentracks.content.data.Track; -import de.dennisguse.opentracks.content.provider.ContentProviderUtils; +import de.dennisguse.opentracks.content.data.TrackPoint; import de.dennisguse.opentracks.util.PreferencesUtils; import de.dennisguse.opentracks.util.UnitConversions; import de.dennisguse.opentracks.viewmodels.IntervalStatistics; @@ -31,12 +33,10 @@ import de.dennisguse.opentracks.viewmodels.IntervalStatisticsModel; /** * A fragment to display the intervals from recorded track. */ -public class IntervalsFragment extends Fragment { +public class IntervalsFragment extends Fragment implements TrackDataListener { private static final String TAG = IntervalsFragment.class.getSimpleName(); - private static final String TRACK_ID_KEY = "trackId"; - private IntervalStatisticsModel viewModel; private ListView intervalListView; protected IntervalStatisticsAdapter.StackMode stackModeListView; @@ -49,7 +49,7 @@ public class IntervalsFragment extends Fragment { private TextView rateLabel; - private Track.Id trackId; + private TrackDataHub trackDataHub; private String category; protected final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (preferences, key) -> { @@ -58,19 +58,12 @@ public class IntervalsFragment extends Fragment { if (adapter != null) { adapter.notifyDataSetChanged(); spinnerAdapter.notifyDataSetChanged(); - setRateLabel(); - intervalChanged(); } } }; - public static Fragment newInstance(Track.Id trackId) { - Bundle bundle = new Bundle(); - bundle.putParcelable(TRACK_ID_KEY, trackId); - - Fragment fragment = new IntervalsFragment(); - fragment.setArguments(bundle); - return fragment; + public static Fragment newInstance() { + return new IntervalsFragment(); } @Override @@ -85,12 +78,6 @@ public class IntervalsFragment extends Fragment { PreferencesUtils.register(getContext(), sharedPreferenceChangeListener); intervalUnit = PreferencesUtils.isMetricUnits(getContext()) ? getContext().getString(R.string.unit_kilometer) : getContext().getString(R.string.unit_mile); - if (savedInstanceState != null) { - setTrackId(savedInstanceState.getParcelable(TRACK_ID_KEY)); - } else { - setTrackId(getArguments().getParcelable(TRACK_ID_KEY)); - } - rateLabel = view.findViewById(R.id.interval_rate); intervalListView = view.findViewById(R.id.interval_list); @@ -98,7 +85,7 @@ public class IntervalsFragment extends Fragment { stackModeListView = IntervalStatisticsAdapter.StackMode.STACK_FROM_TOP; - viewModel = new ViewModelProvider(this).get(IntervalStatisticsModel.class); + viewModel = new IntervalStatisticsModel(); spinnerIntervals = view.findViewById(R.id.spinner_intervals); @@ -124,7 +111,7 @@ public class IntervalsFragment extends Fragment { @Override public void onItemSelected(AdapterView adapterView, View view, int i, long l) { selectedInterval = IntervalStatisticsModel.IntervalOption.values()[i]; - intervalChanged(); + loadIntervals(); } @Override @@ -132,9 +119,18 @@ public class IntervalsFragment extends Fragment { } }); + } - setRateLabel(); - intervalChanged(); + @Override + public void onResume() { + super.onResume(); + resumeTrackDataHub(); + } + + @Override + public void onPause() { + super.onPause(); + pauseTrackDataHub(); } @Override @@ -149,60 +145,108 @@ public class IntervalsFragment extends Fragment { viewModel = null; } - @Override - public void onSaveInstanceState(@NonNull Bundle outState) { - super.onSaveInstanceState(outState); - outState.putParcelable(TRACK_ID_KEY, trackId); - } - - @Override - public void onResume() { - super.onResume(); - setTrackId(trackId); - } - /** * Update intervals through {@link IntervalStatisticsModel} view model. */ - public void intervalChanged() { + protected synchronized void loadIntervals() { if (viewModel == null || intervalListView == null) { return; } - LiveData liveData = viewModel.getIntervalStats(trackId, selectedInterval); - liveData.observe(getActivity(), intervalStatistics -> { - if (intervalStatistics != null) { - adapter = new IntervalStatisticsAdapter(getContext(), intervalStatistics.getIntervalList(), category, stackModeListView); - intervalListView.setAdapter(adapter); + boolean metricUnits = PreferencesUtils.isMetricUnits(getContext()) ? true : false; + IntervalStatistics intervalStatistics = viewModel.getIntervalStats(metricUnits, selectedInterval); + adapter = new IntervalStatisticsAdapter(getContext(), intervalStatistics.getIntervalList(), category, stackModeListView); + intervalListView.setAdapter(adapter); + } + + /** + * 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, true, false, true, true); + } + + /** + * 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()) { + // Set category. + category = track != null ? track.getCategory() : ""; + + // Set rate label. + if (rateLabel != null) { + boolean reportSpeed = PreferencesUtils.isReportSpeed(getContext(), category); + rateLabel.setText(reportSpeed ? R.string.stats_speed : R.string.stats_pace); } - }); - } - - public void setTrackId(Track.Id trackId) { - this.trackId = trackId; - if (this.trackId != null) { - ContentProviderUtils contentProviderUtils = new ContentProviderUtils(getContext()); - Track track = contentProviderUtils.getTrack(this.trackId); - setCategory(track.getCategory()); } } - public void setCategory(String category) { - if (this.category == null || !this.category.equals(category)) { - this.category = category; - setRateLabel(); - intervalChanged(); + @Override + public void clearTrackPoints() { + if (isResumed()) { + viewModel.clear(); } } - private void setRateLabel() { - if (rateLabel != null) { - boolean reportSpeed = PreferencesUtils.isReportSpeed(getContext(), category); - rateLabel.setText(reportSpeed ? R.string.stats_speed : R.string.stats_pace); + @Override + public void onSampledInTrackPoint(TrackPoint trackPoint) { + if (isResumed()) { + viewModel.add(trackPoint); } } - public static class IntervalsRecordingFragment extends IntervalsFragment implements TrackRecordingActivity.OnTrackRecordingListener { + @Override + public void onSampledOutTrackPoint(TrackPoint trackPoint) { + if (isResumed()) { + viewModel.add(trackPoint); + } + } + + @Override + public void onNewTrackPointsDone() { + if (isResumed()) { + runOnUiThread(() -> loadIntervals()); + } + } + + @Override + public void clearMarkers() { + // We don't care. + } + + @Override + public void onNewMarker(Marker marker) { + // We don't care. + } + + @Override + public void onNewMarkersDone() { + // We don't care. + } + + /** + * 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); + } + } + + public static class IntervalsRecordingFragment extends IntervalsFragment { // Refreshing intervals stats it's not so demanding so 5 seconds is enough to balance performance and user experience. private static final long UI_UPDATE_INTERVAL = 5 * UnitConversions.ONE_SECOND_MS; @@ -218,19 +262,13 @@ public class IntervalsFragment extends Fragment { } }; - public static Fragment newInstance(Track.Id trackId) { - Bundle bundle = new Bundle(); - bundle.putParcelable(TRACK_ID_KEY, trackId); - - Fragment fragment = new IntervalsRecordingFragment(); - fragment.setArguments(bundle); - return fragment; + public static Fragment newInstance() { + return new IntervalsRecordingFragment(); } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - ((TrackRecordingActivity) getActivity()).setTrackIdListener(this); intervalHandler = new Handler(); stackModeListView = IntervalStatisticsAdapter.StackMode.STACK_FROM_BOTTOM; } @@ -248,17 +286,7 @@ public class IntervalsFragment extends Fragment { } private void updateIntervals() { - intervalChanged(); - } - - @Override - public void onTrackId(Track.Id trackId) { - setTrackId(trackId); - } - - @Override - public void onCategoryChanged(String category) { - setCategory(category); + loadIntervals(); } } -} \ No newline at end of file +} diff --git a/src/main/java/de/dennisguse/opentracks/viewmodels/IntervalStatisticsModel.java b/src/main/java/de/dennisguse/opentracks/viewmodels/IntervalStatisticsModel.java index a734973b0..87122b924 100644 --- a/src/main/java/de/dennisguse/opentracks/viewmodels/IntervalStatisticsModel.java +++ b/src/main/java/de/dennisguse/opentracks/viewmodels/IntervalStatisticsModel.java @@ -1,56 +1,45 @@ package de.dennisguse.opentracks.viewmodels; -import android.app.Application; -import android.content.Context; - -import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.lifecycle.AndroidViewModel; -import androidx.lifecycle.LiveData; -import androidx.lifecycle.MutableLiveData; +import java.util.ArrayList; import java.util.List; -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.util.PreferencesUtils; import de.dennisguse.opentracks.util.UnitConversions; /** - * This view model is used to load intervals for a track. + * This model is used to load intervals for a track. * It uses a default interval but it can be set from outside to manage the interval length. */ -public class IntervalStatisticsModel extends AndroidViewModel { +public class IntervalStatisticsModel { - private MutableLiveData intervalStats = new MutableLiveData<>(); + private IntervalStatistics intervalStats; + private List trackPoints = new ArrayList<>(); - public IntervalStatisticsModel(@NonNull Application application) { - super(application); + public IntervalStatistics getIntervalStats(boolean metricUnits, @Nullable IntervalOption interval) { + synchronized (trackPoints) { + if (interval == null) { + interval = IntervalOption.OPTION_1; + } + + intervalStats = new IntervalStatistics(); + float distanceInterval = metricUnits ? (float) (interval.getValue() * UnitConversions.KM_TO_M) : (float) (interval.getValue() * UnitConversions.MI_TO_M); + intervalStats.build(trackPoints, distanceInterval); + return intervalStats; + } } - public LiveData getIntervalStats(@Nullable Track.Id trackId, @Nullable IntervalOption interval) { - if (interval == null) { - interval = IntervalOption.OPTION_1; + public void add(TrackPoint trackPoint) { + synchronized (trackPoints) { + trackPoints.add(trackPoint); } - if (trackId != null) { - loadIntervalStats(trackId, interval); - } - return intervalStats; } - private void loadIntervalStats(final Track.Id trackId, IntervalOption interval) { - new Thread(() -> { - Context context = getApplication().getApplicationContext(); - ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context); - List trackPointList = contentProviderUtils.getTrackPoints(trackId); - - IntervalStatistics intervalStatistics = new IntervalStatistics(); - float distanceInterval = PreferencesUtils.isMetricUnits(context) ? (float) (interval.getValue() * UnitConversions.KM_TO_M) : (float) (interval.getValue() * UnitConversions.MI_TO_M); - intervalStatistics.build(trackPointList, distanceInterval); - - intervalStats.postValue(intervalStatistics); - }).start(); + public void clear() { + synchronized (trackPoints) { + trackPoints.clear(); + } } /**