forked from upstream-mirrors/OpenTracks
Aggregate statistics are now computed in the database.
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
package de.dennisguse.opentracks.data;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
|
||||
public record AggregatedStatistic(
|
||||
@NonNull
|
||||
String activityTypeLocalized,
|
||||
int countTracks,
|
||||
|
||||
@NonNull
|
||||
Duration totalMovingTime,
|
||||
@NonNull
|
||||
Distance totalDistance,
|
||||
@NonNull
|
||||
Speed maxSpeed
|
||||
) {
|
||||
|
||||
public Speed getAverageMovingSpeed() {
|
||||
return Speed.of(totalDistance, totalMovingTime);
|
||||
}
|
||||
}
|
||||
@@ -66,6 +66,7 @@ import de.dennisguse.opentracks.util.FileUtils;
|
||||
*
|
||||
* @author Leif Hendrik Wilden
|
||||
*/
|
||||
//TODO Check if do {} while can be replaced by while (cursor.moveToNext)
|
||||
public class ContentProviderUtils {
|
||||
|
||||
private static final String TAG = ContentProviderUtils.class.getSimpleName();
|
||||
@@ -80,6 +81,7 @@ public class ContentProviderUtils {
|
||||
|
||||
private final ContentResolver contentResolver;
|
||||
|
||||
//TODO Check if this can be used in more than one place. If not remove.
|
||||
public interface ContentProviderSelectionInterface {
|
||||
SelectionData buildSelection();
|
||||
}
|
||||
@@ -200,21 +202,7 @@ public class ContentProviderUtils {
|
||||
return tracks;
|
||||
}
|
||||
|
||||
public List<Track> getTracks(ContentProviderSelectionInterface selection) {
|
||||
SelectionData selectionData = selection.buildSelection();
|
||||
ArrayList<Track> tracks = new ArrayList<>();
|
||||
try (Cursor cursor = getTrackCursor(selectionData.selection(), selectionData.selectionArgs(), TracksColumns._ID)) {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
tracks.ensureCapacity(cursor.getCount());
|
||||
do {
|
||||
tracks.add(createTrack(cursor));
|
||||
} while (cursor.moveToNext());
|
||||
}
|
||||
}
|
||||
|
||||
return tracks;
|
||||
}
|
||||
|
||||
//TODO Implement someting like TrackPointIterator: just as a cursor
|
||||
public Cursor searchTracks(String searchQuery) {
|
||||
// Needed, because MARKER_COUNT is a virtual column and has to be explicitly requested.
|
||||
// Used only be TrackListAdapter
|
||||
@@ -311,6 +299,8 @@ public class ContentProviderUtils {
|
||||
values.put(TracksColumns.ACTIVITY_TYPE, track.getActivityType() != null ? track.getActivityType().getId() : null);
|
||||
values.put(TracksColumns.ACTIVITY_TYPE_LOCALIZED, track.getActivityTypeLocalized());
|
||||
values.put(TracksColumns.STARTTIME_OFFSET, track.getZoneOffset().getTotalSeconds());
|
||||
|
||||
//TODO Duplicates createContentValues(Statistics trackStatistics)
|
||||
if (trackStatistics.startTime() != null) {
|
||||
values.put(TracksColumns.STARTTIME, trackStatistics.startTime().toEpochMilli());
|
||||
}
|
||||
@@ -897,4 +887,22 @@ public class ContentProviderUtils {
|
||||
}
|
||||
return sensorStatistics;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public List<AggregatedStatistic> getAggregatedStatisticsForTracks(ContentProviderSelectionInterface selection) {
|
||||
SelectionData data = selection.buildSelection();
|
||||
try (Cursor cursor = contentResolver.query(TracksColumns.CONTENT_URI_AGGREGATED_STATISTICS, null, data.selection(), data.selectionArgs(), null)) {
|
||||
ArrayList<AggregatedStatistic> aggregatedStatistics = new ArrayList<>(cursor.getCount());
|
||||
while (cursor.moveToNext()) {
|
||||
aggregatedStatistics.add(new AggregatedStatistic(
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(TracksColumns.ACTIVITY_TYPE_LOCALIZED)),
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(TracksColumns.TRACK_COUNT)),
|
||||
Duration.ofMillis(cursor.getLong(cursor.getColumnIndexOrThrow(TracksColumns.MOVINGTIME))),
|
||||
Distance.of(cursor.getFloat(cursor.getColumnIndexOrThrow(TracksColumns.TOTALDISTANCE))),
|
||||
Speed.of(cursor.getFloat(cursor.getColumnIndexOrThrow(TracksColumns.MAXSPEED)))
|
||||
));
|
||||
}
|
||||
return aggregatedStatistics;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@@ -65,7 +64,7 @@ public class CustomContentProvider extends ContentProvider {
|
||||
* Finally, it ignores manual pause (SEGMENT_START_MANUAL).
|
||||
*/
|
||||
private final String SENSOR_STATS_QUERY =
|
||||
"WITH time_select as " +
|
||||
"WITH time_select AS " +
|
||||
"(SELECT t1." + TrackPointsColumns.TIME + " * (t1." + TrackPointsColumns.TYPE + " NOT IN (" + TrackPoint.Type.SEGMENT_START_MANUAL.type_db + ")) time_value " +
|
||||
"FROM " + TrackPointsColumns.TABLE_NAME + " t1 " +
|
||||
"WHERE t1." + TrackPointsColumns._ID + " > t." + TrackPointsColumns._ID + " AND t1." + TrackPointsColumns.TRACKID + " = ? ORDER BY _id LIMIT 1) " +
|
||||
@@ -101,6 +100,7 @@ public class CustomContentProvider extends ContentProvider {
|
||||
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.CONTENT_URI.getPath(), UrlType.TRACKS.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.CONTENT_URI_SENSOR_STATS.getPath() + "/#", UrlType.TRACKS_SENSOR_STATS.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.CONTENT_URI_AGGREGATED_STATISTICS.getPath(), UrlType.TRACKS_AGGREGATED_STATISTICS.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.CONTENT_URI.getPath() + "/*", UrlType.TRACKS_BY_ID.ordinal());
|
||||
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, MarkerColumns.CONTENT_URI.getPath(), UrlType.MARKERS.ordinal());
|
||||
@@ -223,6 +223,8 @@ public class CustomContentProvider extends ContentProvider {
|
||||
public Cursor query(@NonNull Uri url, String[] projection, String selection, String[] selectionArgs, String sort) {
|
||||
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
|
||||
String sortOrder = null;
|
||||
String groupBy = null;
|
||||
//TODO queryBuilder.appendWhere() should use selectionArgs?
|
||||
switch (getUrlType(url)) {
|
||||
case TRACKPOINTS -> {
|
||||
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
|
||||
@@ -249,9 +251,23 @@ public class CustomContentProvider extends ContentProvider {
|
||||
queryBuilder.appendWhere(TracksColumns._ID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
||||
}
|
||||
case TRACKS_SENSOR_STATS -> {
|
||||
//TODO Replace with SQLQueryBuilder?
|
||||
long trackId = ContentUris.parseId(url);
|
||||
return db.rawQuery(SENSOR_STATS_QUERY, new String[]{String.valueOf(trackId), String.valueOf(trackId)});
|
||||
}
|
||||
case TRACKS_AGGREGATED_STATISTICS -> {
|
||||
queryBuilder.setTables(TracksColumns.TABLE_NAME);
|
||||
projection = new String[]{
|
||||
"COUNT(*) AS " + TracksColumns.TRACK_COUNT,
|
||||
TracksColumns.ACTIVITY_TYPE_LOCALIZED,
|
||||
"SUM(" + TracksColumns.MOVINGTIME + ") AS " + TracksColumns.MOVINGTIME,
|
||||
"SUM(" + TracksColumns.TOTALDISTANCE + ") AS " + TracksColumns.TOTALDISTANCE,
|
||||
"MAX(" + TracksColumns.MAXSPEED + ") AS " + TracksColumns.MAXSPEED
|
||||
};
|
||||
|
||||
sortOrder = TracksColumns.TRACK_COUNT + " DESC";
|
||||
groupBy = TracksColumns.ACTIVITY_TYPE_LOCALIZED;
|
||||
}
|
||||
case MARKERS -> {
|
||||
queryBuilder.setTables(MarkerColumns.TABLE_NAME);
|
||||
sortOrder = sort != null ? sort : MarkerColumns.DEFAULT_SORT_ORDER;
|
||||
@@ -266,7 +282,7 @@ public class CustomContentProvider extends ContentProvider {
|
||||
}
|
||||
default -> throw new IllegalArgumentException("Unknown url " + url);
|
||||
}
|
||||
Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder);
|
||||
Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, groupBy, null, sortOrder);
|
||||
cursor.setNotificationUri(getContext().getContentResolver(), url);
|
||||
return cursor;
|
||||
}
|
||||
@@ -379,14 +395,14 @@ public class CustomContentProvider extends ContentProvider {
|
||||
throw new SQLException("Failed to insert a marker " + url);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
enum UrlType {
|
||||
private enum UrlType {
|
||||
TRACKPOINTS,
|
||||
TRACKPOINTS_BY_ID,
|
||||
TRACKPOINTS_BY_TRACKID,
|
||||
TRACKS,
|
||||
TRACKS_BY_ID,
|
||||
TRACKS_SENSOR_STATS,
|
||||
TRACKS_AGGREGATED_STATISTICS,
|
||||
MARKERS,
|
||||
MARKERS_BY_ID,
|
||||
MARKERS_BY_TRACKID
|
||||
|
||||
@@ -19,6 +19,8 @@ public record Distance(double distance_m) {
|
||||
return Distance.of((double) distance_m);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated //TODO Remove
|
||||
public static Distance of(String distance_m) {
|
||||
return of(Float.parseFloat(distance_m));
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ public record Speed(double speed_mps) {
|
||||
return new Speed(speed_mps);
|
||||
}
|
||||
|
||||
@Deprecated //TODO Remove
|
||||
public static Speed of(String speed_mps) {
|
||||
return of(Float.parseFloat(speed_mps));
|
||||
}
|
||||
|
||||
@@ -35,9 +35,6 @@ public record Statistics(
|
||||
}
|
||||
|
||||
public Speed getAverageSpeed() {
|
||||
if (totalTime.isZero()) {
|
||||
return Speed.ZERO;
|
||||
}
|
||||
return Speed.of(totalDistance, totalTime);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ public interface TracksColumns extends BaseColumns {
|
||||
String TABLE_NAME = "tracks";
|
||||
Uri CONTENT_URI = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME);
|
||||
Uri CONTENT_URI_SENSOR_STATS = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME + "/sensorstats");
|
||||
Uri CONTENT_URI_AGGREGATED_STATISTICS = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME + "/aggregated/");
|
||||
String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.de.dennisguse.track";
|
||||
String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.de.dennisguse.track";
|
||||
String DEFAULT_SORT_ORDER = _ID;
|
||||
@@ -46,7 +47,9 @@ public interface TracksColumns extends BaseColumns {
|
||||
String STARTTIME_OFFSET = "time_offset"; // in plus/minus in seconds
|
||||
String STARTTIME = "time_start"; // track start time
|
||||
String STOPTIME = "time_stop"; // track stop time
|
||||
|
||||
String MARKER_COUNT = "markerCount"; // the numbers of markers (virtual column)
|
||||
String TRACK_COUNT = "trackCount"; // the numbers of markers (virtual column)
|
||||
|
||||
String TOTALDISTANCE = "distance"; // total distance
|
||||
String TOTALTIME = "duration_total";
|
||||
|
||||
Reference in New Issue
Block a user