forked from upstream-mirrors/OpenTracks
Compare commits
83 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 163af2dab2 | |||
| 393e2022a1 | |||
| 793928a5a6 | |||
| 50bb0b14b4 | |||
| 8a08e7b2b5 | |||
| e9e9fdcc3f | |||
| 83c471ccb6 | |||
| 17479c438b | |||
| 943c80f9ec | |||
| 23751306a4 | |||
| 78b712a291 | |||
| f8432adaa0 | |||
| 80b26f2a04 | |||
| 41e6e80cfc | |||
| 7e72b29c3f | |||
| 860df0197e | |||
| f1d791a62c | |||
| e9551bea36 | |||
| 577b978a5c | |||
| a689b6eb2c | |||
| ccd93dd17d | |||
| f4fe1d8d97 | |||
| d1290c8f22 | |||
| a340cb57de | |||
| 75b8f3b9cf | |||
| 9462221a28 | |||
| af82eae8a4 | |||
| 1c4946e85b | |||
| fd1b7b8af9 | |||
| eb02c4c469 | |||
| 4714171e21 | |||
| 4bff376442 | |||
| c14413cbe7 | |||
| 778fb4b346 | |||
| 130552f0eb | |||
| 41eca663e8 | |||
| 6c79702075 | |||
| 6d109a1c5a | |||
| b71904a3bd | |||
| ef28784682 | |||
| 05b7242191 | |||
| 94c69f92a6 | |||
| 8f12ba3d48 | |||
| 190a572951 | |||
| 7ae8809101 | |||
| 19a7ef3217 | |||
| f261e8ab9d | |||
| a41160229d | |||
| 2fa8b998f9 | |||
| 99847dbed9 | |||
| 483736637e | |||
| e133743547 | |||
| eb2c2b4d78 | |||
| 1409537e56 | |||
| 76c3699518 | |||
| 5675dc800d | |||
| e1e7c24d9f | |||
| 2733071a4d | |||
| 2255624235 | |||
| 55fa6d2d79 | |||
| 96371336f4 | |||
| 1862e38a20 | |||
| 397e056f6f | |||
| eb79f2277a | |||
| ae8833d21b | |||
| e8017a4e9d | |||
| cea180f871 | |||
| be1b6ece9d | |||
| 0592863d3c | |||
| 18bc100d5a | |||
| 4476a15558 | |||
| 215c28be85 | |||
| a429fbe4df | |||
| 6b470c7fdc | |||
| 6b690f38ad | |||
| f138826d60 | |||
| 14ac29095d | |||
| a3f4de7d18 | |||
| 6392e88dc7 | |||
| 1e913d6bb5 | |||
| ced8454f59 | |||
| 974a5afeb1 | |||
| 9c9bdde6b9 |
@@ -19,9 +19,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
|
|
||||||
- run: git checkout HEAD^2
|
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v1
|
uses: github/codeql-action/init@v1
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
* Polar OH1
|
* Polar OH1
|
||||||
Has updatable firmware that requires a user account at polar.com
|
Has updatable firmware that requires a user account at polar.com
|
||||||
* Wahoo Tickr (Model: WFBTHR02)
|
* Wahoo Tickr (Model: WFBTHR02)
|
||||||
|
* Mi Band 3, Amazfit Band 5, Amazfit Bip. These devices are manufactured by Huami, and other Huami devices will very likely work. Theoretically, this sensor must be enabled, but testing reveals that it works even without enabling. For details on how to enable see [Gadgetbridge wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Huami-Heartrate-measurement#bluetooth-heart-rate-sensor)
|
||||||
|
|
||||||
## CSCP: Cycling Cadence and Speed Service
|
## CSCP: Cycling Cadence and Speed Service
|
||||||
|
|
||||||
@@ -40,3 +41,4 @@ Also the distance is not computed.
|
|||||||
* QUARQ Red DZero Powermeter
|
* QUARQ Red DZero Powermeter
|
||||||
* Tacx Satori Smart
|
* Tacx Satori Smart
|
||||||
* Wahoo Kickr v4.0
|
* Wahoo Kickr v4.0
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -27,7 +27,7 @@ cp "$CHANGELOG_DIR/next_release.txt" "$CHANGELOG_FILE"
|
|||||||
sed -i "s/(versionName)/$VERSIONNAME/" "$CHANGELOG_FILE"
|
sed -i "s/(versionName)/$VERSIONNAME/" "$CHANGELOG_FILE"
|
||||||
|
|
||||||
echo -e "\nGit commit since last release\n------------"
|
echo -e "\nGit commit since last release\n------------"
|
||||||
git log $(git describe --tags --abbrev=0)..HEAD --no-merges --oneline
|
git log "$(git describe --tags --abbrev=0)..HEAD" --no-merges --oneline
|
||||||
|
|
||||||
echo -e "\nPlease edit the changelog for the new release"
|
echo -e "\nPlease edit the changelog for the new release"
|
||||||
gedit -w "$CHANGELOG_FILE"
|
gedit -w "$CHANGELOG_FILE"
|
||||||
|
|||||||
+5
-4
@@ -6,7 +6,7 @@ buildscript {
|
|||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:4.1.1'
|
classpath 'com.android.tools.build:gradle:4.1.2'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,8 +51,8 @@ android {
|
|||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "de.dennisguse.opentracks"
|
applicationId "de.dennisguse.opentracks"
|
||||||
versionCode 3740
|
versionCode 3823
|
||||||
versionName "v3.13.2"
|
versionName "v3.14.3"
|
||||||
|
|
||||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||||
|
|
||||||
@@ -86,6 +86,7 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0-alpha03'
|
||||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1'
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1'
|
||||||
|
|
||||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||||
@@ -98,5 +99,5 @@ dependencies {
|
|||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
||||||
androidTestImplementation 'androidx.test:rules:1.3.0'
|
androidTestImplementation 'androidx.test:rules:1.3.0'
|
||||||
androidTestImplementation 'androidx.test:runner:1.3.0'
|
androidTestImplementation 'androidx.test:runner:1.3.0'
|
||||||
androidTestImplementation 'org.mockito:mockito-android:3.6.0'
|
androidTestImplementation 'org.mockito:mockito-android:3.7.7'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
v3.14.0: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Properly support for right-to-left
|
||||||
|
- Import: faster cleanup after import failure
|
||||||
|
- Text in charts scales according to Android settings
|
||||||
|
- Heart rate: support for Huami devices (requires Gadgetbridge)
|
||||||
|
|
||||||
|
Developer:
|
||||||
|
- Import is now handled in-memory
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
v3.14.1: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Added elevation loss (via barometer)
|
||||||
|
- Updated translations (cz, gl, it, nb, pl, pt, sp)
|
||||||
|
- Markers can be shared from list
|
||||||
|
- Actionbar icon (top left) to go back
|
||||||
|
-
|
||||||
|
Bugfix:
|
||||||
|
- Potential crash on import
|
||||||
|
|
||||||
|
Developer:
|
||||||
|
- Implemented basic EspressoUI test
|
||||||
|
- Reference resourceIds via if rather than switch/case
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
v3.14.2: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Downgrade database from upcoming release v3.15.0
|
||||||
|
- Import/Export UI improved
|
||||||
|
- Import offers to show imported track
|
||||||
|
- Marker list performance improved
|
||||||
|
|
||||||
|
Developer:
|
||||||
|
- Track.Id/Marker.Id: use null
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
v3.14.3: OpenTracks
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Downgrading database from upcoming release v3.15.0
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
package de.dennisguse.opentracks;
|
||||||
|
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.test.espresso.UiController;
|
||||||
|
import androidx.test.espresso.ViewAction;
|
||||||
|
import androidx.test.espresso.ViewInteraction;
|
||||||
|
import androidx.test.espresso.action.MotionEvents;
|
||||||
|
import androidx.test.ext.junit.rules.ActivityScenarioRule;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import androidx.test.filters.LargeTest;
|
||||||
|
import androidx.test.rule.GrantPermissionRule;
|
||||||
|
|
||||||
|
import com.google.android.material.tabs.TabLayout;
|
||||||
|
|
||||||
|
import org.hamcrest.Matcher;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import static androidx.test.espresso.Espresso.onView;
|
||||||
|
import static androidx.test.espresso.action.ViewActions.click;
|
||||||
|
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||||
|
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||||
|
|
||||||
|
@LargeTest
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class EspressoUITest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ActivityScenarioRule<TrackListActivity> mActivityTestRule = new ActivityScenarioRule<>(TrackListActivity.class);
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public GrantPermissionRule mGrantPermissionRule = GrantPermissionRule.grant("android.permission.ACCESS_FINE_LOCATION");
|
||||||
|
|
||||||
|
@LargeTest
|
||||||
|
@Test
|
||||||
|
public void record_pause_resume_stop() {
|
||||||
|
{
|
||||||
|
// TrackListActivity: start recording
|
||||||
|
ViewInteraction trackControllerRecordButton = onView(withId(R.id.track_controller_record));
|
||||||
|
trackControllerRecordButton.perform(click());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// TrackRecordingActivity
|
||||||
|
ViewInteraction trackControllerRecordButton = onView(withId(R.id.track_controller_record));
|
||||||
|
ViewInteraction trackControllerStopButton = onView(withId(R.id.track_controller_stop));
|
||||||
|
|
||||||
|
// wait; stay recording
|
||||||
|
trackControllerRecordButton.perform(waitFor(5000));
|
||||||
|
|
||||||
|
//pause
|
||||||
|
trackControllerRecordButton.perform(veryLongTouch(1600));
|
||||||
|
|
||||||
|
// wait; stay paused
|
||||||
|
trackControllerRecordButton.perform(waitFor(1000));
|
||||||
|
|
||||||
|
// resume
|
||||||
|
trackControllerRecordButton.perform(click());
|
||||||
|
|
||||||
|
// wait; stay recording
|
||||||
|
trackControllerRecordButton.perform(waitFor(1000));
|
||||||
|
|
||||||
|
// stop;
|
||||||
|
trackControllerStopButton.perform(veryLongTouch(1600));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@LargeTest
|
||||||
|
@Test
|
||||||
|
public void record_move_through_tabs() {
|
||||||
|
{
|
||||||
|
// TrackListActivity: start recording
|
||||||
|
ViewInteraction trackControllerRecordButton = onView(withId(R.id.track_controller_record));
|
||||||
|
trackControllerRecordButton.perform(click());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// TrackRecordingActivity
|
||||||
|
ViewInteraction tabLayout = onView(withId(R.id.track_detail_activity_tablayout));
|
||||||
|
ViewInteraction trackControllerStopButton = onView(withId(R.id.track_controller_stop));
|
||||||
|
|
||||||
|
tabLayout.perform(selectTabAtIndex(1));
|
||||||
|
tabLayout.perform(waitFor(1000));
|
||||||
|
|
||||||
|
tabLayout.perform(selectTabAtIndex(2));
|
||||||
|
tabLayout.perform(waitFor(1000));
|
||||||
|
|
||||||
|
tabLayout.perform(selectTabAtIndex(3));
|
||||||
|
tabLayout.perform(waitFor(1000));
|
||||||
|
|
||||||
|
tabLayout.perform(selectTabAtIndex(0));
|
||||||
|
tabLayout.perform(waitFor(1000));
|
||||||
|
|
||||||
|
// stop
|
||||||
|
trackControllerStopButton.perform(veryLongTouch(1600));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ViewAction veryLongTouch(final int duration_ms) {
|
||||||
|
return new ViewAction() {
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return "Perform long touch.";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Matcher<View> getConstraints() {
|
||||||
|
return isDisplayed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void perform(UiController uiController, final View view) {
|
||||||
|
// Get view absolute position
|
||||||
|
int[] location = new int[2];
|
||||||
|
view.getLocationOnScreen(location);
|
||||||
|
|
||||||
|
// Offset coordinates by view position
|
||||||
|
float[] coordinates = new float[]{location[0] + 1, location[1] + 1};
|
||||||
|
|
||||||
|
// Send down event, pause, and send up
|
||||||
|
MotionEvent down = MotionEvents.sendDown(uiController, coordinates, new float[]{1f, 1f}).down;
|
||||||
|
uiController.loopMainThreadForAtLeast(duration_ms);
|
||||||
|
MotionEvents.sendUp(uiController, down, coordinates);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ViewAction waitFor(final long duration_ms) {
|
||||||
|
return new ViewAction() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return "Wait for milliseconds.";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Matcher<View> getConstraints() {
|
||||||
|
return isDisplayed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void perform(UiController uiController, final View view) {
|
||||||
|
uiController.loopMainThreadForAtLeast(duration_ms);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ViewAction selectTabAtIndex(final int index) {
|
||||||
|
return new ViewAction() {
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return "Selecting tab.";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Matcher<View> getConstraints() {
|
||||||
|
return isDisplayed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void perform(UiController uiController, View view) {
|
||||||
|
TabLayout tabLayout = (TabLayout) view;
|
||||||
|
tabLayout.getTabAt(index).select();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -48,7 +48,9 @@ public class ChartValueSeriesTest {
|
|||||||
R.string.description_elevation_metric,
|
R.string.description_elevation_metric,
|
||||||
R.string.description_elevation_imperial,
|
R.string.description_elevation_imperial,
|
||||||
R.color.chart_elevation_fill,
|
R.color.chart_elevation_fill,
|
||||||
R.color.chart_elevation_border) {
|
R.color.chart_elevation_border,
|
||||||
|
15,
|
||||||
|
18) {
|
||||||
@Override
|
@Override
|
||||||
double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||||
return chartPoint.getElevation();
|
return chartPoint.getElevation();
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ public class DescriptionGeneratorTest {
|
|||||||
stats.setMaxElevation(550);
|
stats.setMaxElevation(550);
|
||||||
stats.setMinElevation(-500);
|
stats.setMinElevation(-500);
|
||||||
stats.setTotalElevationGain(6000);
|
stats.setTotalElevationGain(6000);
|
||||||
|
stats.setTotalElevationLoss(6000);
|
||||||
stats.setStartTime_ms(START_TIME);
|
stats.setStartTime_ms(START_TIME);
|
||||||
track.setTrackStatistics(stats);
|
track.setTrackStatistics(stats);
|
||||||
track.setCategory("hiking");
|
track.setCategory("hiking");
|
||||||
@@ -84,6 +85,7 @@ public class DescriptionGeneratorTest {
|
|||||||
+ "Max elevation: 550 m (1804 ft)<br>"
|
+ "Max elevation: 550 m (1804 ft)<br>"
|
||||||
+ "Min elevation: -500 m (-1640 ft)<br>"
|
+ "Min elevation: -500 m (-1640 ft)<br>"
|
||||||
+ "Elevation gain: 6000 m (19685 ft)<br>"
|
+ "Elevation gain: 6000 m (19685 ft)<br>"
|
||||||
|
+ "Elevation loss: 6000 m (19685 ft)<br>"
|
||||||
+ "Recorded: " + StringUtils.formatDateTime(context, START_TIME) + "<br>";
|
+ "Recorded: " + StringUtils.formatDateTime(context, START_TIME) + "<br>";
|
||||||
|
|
||||||
assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true));
|
assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true));
|
||||||
@@ -100,6 +102,7 @@ public class DescriptionGeneratorTest {
|
|||||||
stats.setMaxElevation(Double.POSITIVE_INFINITY);
|
stats.setMaxElevation(Double.POSITIVE_INFINITY);
|
||||||
stats.setMinElevation(Double.NEGATIVE_INFINITY);
|
stats.setMinElevation(Double.NEGATIVE_INFINITY);
|
||||||
stats.setTotalElevationGain(6000);
|
stats.setTotalElevationGain(6000);
|
||||||
|
stats.setTotalElevationLoss(6000);
|
||||||
stats.setStartTime_ms(START_TIME);
|
stats.setStartTime_ms(START_TIME);
|
||||||
track.setTrackStatistics(stats);
|
track.setTrackStatistics(stats);
|
||||||
track.setCategory("hiking");
|
track.setCategory("hiking");
|
||||||
@@ -118,6 +121,7 @@ public class DescriptionGeneratorTest {
|
|||||||
+ "Average moving pace: 0:15 min/km (0:24 min/mi)<br>"
|
+ "Average moving pace: 0:15 min/km (0:24 min/mi)<br>"
|
||||||
+ "Fastest pace: 0:10 min/km (0:16 min/mi)<br>"
|
+ "Fastest pace: 0:10 min/km (0:16 min/mi)<br>"
|
||||||
+ "Elevation gain: 6000 m (19685 ft)<br>"
|
+ "Elevation gain: 6000 m (19685 ft)<br>"
|
||||||
|
+ "Elevation loss: 6000 m (19685 ft)<br>"
|
||||||
+ "Recorded: " + StringUtils.formatDateTime(context, START_TIME) + "<br>";
|
+ "Recorded: " + StringUtils.formatDateTime(context, START_TIME) + "<br>";
|
||||||
|
|
||||||
assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true));
|
assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true));
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import android.util.Pair;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.util.FileUtils;
|
import de.dennisguse.opentracks.util.FileUtils;
|
||||||
@@ -17,6 +19,7 @@ public class TestDataUtil {
|
|||||||
public static final double INITIAL_LONGITUDE = -57.0;
|
public static final double INITIAL_LONGITUDE = -57.0;
|
||||||
public static final double ALTITUDE_INTERVAL = 2.5;
|
public static final double ALTITUDE_INTERVAL = 2.5;
|
||||||
public static final float ELEVATION_GAIN = 3;
|
public static final float ELEVATION_GAIN = 3;
|
||||||
|
public static final float ELEVATION_LOSS = 3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a track without any trackPoints.
|
* Create a track without any trackPoints.
|
||||||
@@ -35,19 +38,19 @@ public class TestDataUtil {
|
|||||||
* @param trackId the trackId of the track
|
* @param trackId the trackId of the track
|
||||||
* @param numPoints the trackPoints number in the track
|
* @param numPoints the trackPoints number in the track
|
||||||
*/
|
*/
|
||||||
public static Pair<Track, TrackPoint[]> createTrack(Track.Id trackId, int numPoints) {
|
public static Pair<Track, List<TrackPoint>> createTrack(Track.Id trackId, int numPoints) {
|
||||||
Track track = createTrack(trackId);
|
Track track = createTrack(trackId);
|
||||||
|
|
||||||
TrackPoint[] trackPoints = new TrackPoint[numPoints];
|
List<TrackPoint> trackPoints = new ArrayList<>(numPoints);
|
||||||
for (int i = 0; i < numPoints; i++) {
|
for (int i = 0; i < numPoints; i++) {
|
||||||
trackPoints[i] = createTrackPoint(i);
|
trackPoints.add(createTrackPoint(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Pair<>(track, trackPoints);
|
return new Pair<>(track, trackPoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Track createTrackAndInsert(ContentProviderUtils contentProviderUtils, Track.Id trackId, int numPoints) {
|
public static Track createTrackAndInsert(ContentProviderUtils contentProviderUtils, Track.Id trackId, int numPoints) {
|
||||||
Pair<Track, TrackPoint[]> pair = createTrack(trackId, numPoints);
|
Pair<Track, List<TrackPoint>> pair = createTrack(trackId, numPoints);
|
||||||
|
|
||||||
insertTrackWithLocations(contentProviderUtils, pair.first, pair.second);
|
insertTrackWithLocations(contentProviderUtils, pair.first, pair.second);
|
||||||
|
|
||||||
@@ -66,12 +69,13 @@ public class TestDataUtil {
|
|||||||
trackPoint.setAccuracy((float) i / 100.0f);
|
trackPoint.setAccuracy((float) i / 100.0f);
|
||||||
trackPoint.setAltitude(i * ALTITUDE_INTERVAL);
|
trackPoint.setAltitude(i * ALTITUDE_INTERVAL);
|
||||||
trackPoint.setTime(i + 1);
|
trackPoint.setTime(i + 1);
|
||||||
trackPoint.setSpeed(5f + (i / 10));
|
trackPoint.setSpeed(5f + (i / 10f));
|
||||||
|
|
||||||
trackPoint.setHeartRate_bpm(100f + i);
|
trackPoint.setHeartRate_bpm(100f + i);
|
||||||
trackPoint.setCyclingCadence_rpm(300f + i);
|
trackPoint.setCyclingCadence_rpm(300f + i);
|
||||||
trackPoint.setPower(400f + i);
|
trackPoint.setPower(400f + i);
|
||||||
trackPoint.setElevationGain(ELEVATION_GAIN);
|
trackPoint.setElevationGain(ELEVATION_GAIN);
|
||||||
|
trackPoint.setElevationLoss(ELEVATION_LOSS);
|
||||||
return trackPoint;
|
return trackPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,25 +85,25 @@ public class TestDataUtil {
|
|||||||
* @param track track to be inserted
|
* @param track track to be inserted
|
||||||
* @param trackPoints trackPoints to be inserted
|
* @param trackPoints trackPoints to be inserted
|
||||||
*/
|
*/
|
||||||
public static void insertTrackWithLocations(ContentProviderUtils contentProviderUtils, Track track, TrackPoint[] trackPoints) {
|
public static void insertTrackWithLocations(ContentProviderUtils contentProviderUtils, Track track, List<TrackPoint> trackPoints) {
|
||||||
contentProviderUtils.insertTrack(track);
|
contentProviderUtils.insertTrack(track);
|
||||||
contentProviderUtils.bulkInsertTrackPoint(trackPoints, track.getId());
|
contentProviderUtils.bulkInsertTrackPoint(trackPoints, track.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a Waypoint with a photo.
|
* Creates a Marker with a photo.
|
||||||
*
|
*
|
||||||
* @param context The context.
|
* @param context The context.
|
||||||
* @param trackId The track id.
|
* @param trackId The track id.
|
||||||
* @param location The location.
|
* @param location The location.
|
||||||
* @return the Waypoint created.
|
* @return the Marker created.
|
||||||
*/
|
*/
|
||||||
public static Marker createWaypointWithPhoto(Context context, Track.Id trackId, Location location) throws IOException {
|
public static Marker createMarkerWithPhoto(Context context, Track.Id trackId, Location location) throws IOException {
|
||||||
File dstFile = new File(FileUtils.getImageUrl(context, trackId));
|
File dstFile = new File(FileUtils.getImageUrl(context, trackId));
|
||||||
dstFile.createNewFile();
|
dstFile.createNewFile();
|
||||||
Uri photoUri = FileUtils.getUriForFile(context, dstFile);
|
Uri photoUri = FileUtils.getUriForFile(context, dstFile);
|
||||||
String photoUrl = photoUri.toString();
|
String photoUrl = photoUri.toString();
|
||||||
|
|
||||||
return new Marker("Waypoint name", "Waypoint description", "Waypoint category", "", trackId, 0.0, 0, location, photoUrl);
|
return new Marker("Marker name", "Marker description", "Marker category", "", trackId, 0.0, 0, location, photoUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+181
-262
@@ -34,9 +34,9 @@ import org.mockito.junit.MockitoJUnitRunner;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.data.Marker;
|
import de.dennisguse.opentracks.content.data.Marker;
|
||||||
import de.dennisguse.opentracks.content.data.MarkerColumns;
|
import de.dennisguse.opentracks.content.data.MarkerColumns;
|
||||||
@@ -64,8 +64,9 @@ import static org.mockito.Mockito.when;
|
|||||||
*/
|
*/
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
public class CustomContentProviderUtilsTest {
|
public class CustomContentProviderUtilsTest {
|
||||||
|
|
||||||
private static final String NAME_PREFIX = "test name";
|
private static final String NAME_PREFIX = "test name";
|
||||||
private static final String MOCK_DESC = "Mock Next Waypoint Desc!";
|
private static final String MOCK_DESC = "Mock Next Marker Desc!";
|
||||||
private static final String TEST_DESC = "Test Desc!";
|
private static final String TEST_DESC = "Test Desc!";
|
||||||
private static final String TEST_DESC_NEW = "Test Desc new!";
|
private static final String TEST_DESC_NEW = "Test Desc new!";
|
||||||
private static final String TEST_NAME_NEW = "Test Name new!";
|
private static final String TEST_NAME_NEW = "Test Name new!";
|
||||||
@@ -87,50 +88,37 @@ public class CustomContentProviderUtilsTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLocationIterator_noPoints() {
|
public void testLocationIterator_noPoints() {
|
||||||
testIterator(new Track.Id(1), 0, 1, false);
|
testIterator(new Track.Id(1), 0, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLocationIterator_noBatchAscending() {
|
public void testLocationIterator_noBatchAscending() {
|
||||||
testIterator(new Track.Id(1), 50, 100, false);
|
testIterator(new Track.Id(1), 50, 100);
|
||||||
testIterator(new Track.Id(2), 50, 50, false);
|
testIterator(new Track.Id(2), 50, 50);
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLocationIterator_noBatchDescending() {
|
|
||||||
testIterator(new Track.Id(1), 50, 100, true);
|
|
||||||
testIterator(new Track.Id(2), 50, 50, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLocationIterator_batchAscending() {
|
public void testLocationIterator_batchAscending() {
|
||||||
testIterator(new Track.Id(1), 50, 11, false);
|
testIterator(new Track.Id(1), 50, 11);
|
||||||
testIterator(new Track.Id(2), 50, 25, false);
|
testIterator(new Track.Id(2), 50, 25);
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLocationIterator_batchDescending() {
|
|
||||||
testIterator(new Track.Id(1), 50, 11, true);
|
|
||||||
testIterator(new Track.Id(2), 50, 25, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLocationIterator_largeTrack() {
|
public void testLocationIterator_largeTrack() {
|
||||||
testIterator(new Track.Id(1), 20000, 2000, false);
|
testIterator(new Track.Id(1), 20000, 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void testIterator(Track.Id trackId, int numPoints, int batchSize, boolean descending) {
|
private void testIterator(Track.Id trackId, int numPoints, int batchSize) {
|
||||||
long lastPointId = initializeTrack(trackId, numPoints);
|
long lastPointId = initializeTrack(trackId, numPoints);
|
||||||
contentProviderUtils.setDefaultCursorBatchSize(batchSize);
|
contentProviderUtils.setDefaultCursorBatchSize(batchSize);
|
||||||
List<TrackPoint> locations = new ArrayList<>(numPoints);
|
List<TrackPoint> locations = new ArrayList<>(numPoints);
|
||||||
try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(trackId, -1L, descending)) {
|
try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(trackId, null)) {
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
TrackPoint loc = it.next();
|
TrackPoint trackPoint = it.next();
|
||||||
assertNotNull(loc);
|
assertNotNull(trackPoint);
|
||||||
locations.add(loc);
|
locations.add(trackPoint);
|
||||||
// Make sure the IDs are returned in the right order.
|
// Make sure the IDs are returned in the right order.
|
||||||
assertEquals(descending ? lastPointId - locations.size() + 1
|
assertEquals(lastPointId - numPoints + locations.size(), trackPoint.getId().getId());
|
||||||
: lastPointId - numPoints + locations.size(), it.getTrackPointId());
|
|
||||||
}
|
}
|
||||||
assertEquals(numPoints, locations.size());
|
assertEquals(numPoints, locations.size());
|
||||||
}
|
}
|
||||||
@@ -144,24 +132,24 @@ public class CustomContentProviderUtilsTest {
|
|||||||
track = contentProviderUtils.getTrack(id);
|
track = contentProviderUtils.getTrack(id);
|
||||||
assertNotNull(track);
|
assertNotNull(track);
|
||||||
|
|
||||||
TrackPoint[] trackPoints = new TrackPoint[numPoints];
|
List<TrackPoint> trackPoints = new ArrayList<>(numPoints);
|
||||||
for (int i = 0; i < numPoints; ++i) {
|
for (int i = 0; i < numPoints; ++i) {
|
||||||
Location loc = new Location("test");
|
Location loc = new Location("test");
|
||||||
loc.setLatitude(37.0 + (double) i / 10000.0);
|
loc.setLatitude(37.0 + (double) i / 10000.0);
|
||||||
loc.setLongitude(57.0 - (double) i / 10000.0);
|
loc.setLongitude(57.0 - (double) i / 10000.0);
|
||||||
loc.setAccuracy((float) i / 100.0f);
|
loc.setAccuracy((float) i / 100.0f);
|
||||||
loc.setAltitude(i * 2.5);
|
loc.setAltitude(i * 2.5);
|
||||||
trackPoints[i] = new TrackPoint(loc);
|
trackPoints.add(new TrackPoint(loc));
|
||||||
}
|
}
|
||||||
contentProviderUtils.bulkInsertTrackPoint(trackPoints, id);
|
contentProviderUtils.bulkInsertTrackPoint(trackPoints, id);
|
||||||
|
|
||||||
// Load all inserted trackPoints.
|
// Load all inserted trackPoints.
|
||||||
long lastPointId = -1;
|
long lastPointId = -1;
|
||||||
int counter = 0;
|
int counter = 0;
|
||||||
try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(id, -1L, false)) {
|
try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(id, null)) {
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
it.next();
|
TrackPoint trackPoint = it.next();
|
||||||
lastPointId = it.getTrackPointId();
|
lastPointId = trackPoint.getId().getId();
|
||||||
counter++;
|
counter++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -198,7 +186,7 @@ public class CustomContentProviderUtilsTest {
|
|||||||
when(cursorMock.isNull(columnIndex)).thenReturn(false);
|
when(cursorMock.isNull(columnIndex)).thenReturn(false);
|
||||||
when(cursorMock.getString(columnIndex)).thenReturn(name);
|
when(cursorMock.getString(columnIndex)).thenReturn(name);
|
||||||
|
|
||||||
Track track = contentProviderUtils.createTrack(cursorMock);
|
Track track = ContentProviderUtils.createTrack(cursorMock);
|
||||||
assertEquals(trackId, track.getId());
|
assertEquals(trackId, track.getId());
|
||||||
assertEquals(name, track.getName());
|
assertEquals(name, track.getName());
|
||||||
}
|
}
|
||||||
@@ -208,20 +196,20 @@ public class CustomContentProviderUtilsTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testDeleteAllTracks() {
|
public void testDeleteAllTracks() {
|
||||||
// Insert track, points and waypoint at first.
|
// Insert track, points and marker at first.
|
||||||
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
||||||
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||||
|
|
||||||
Marker waypoint = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId));
|
Marker marker = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId));
|
||||||
contentProviderUtils.insertMarker(waypoint);
|
contentProviderUtils.insertMarker(marker);
|
||||||
|
|
||||||
ContentResolver contentResolver = context.getContentResolver();
|
ContentResolver contentResolver = context.getContentResolver();
|
||||||
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
|
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
|
||||||
assertEquals(1, tracksCursor.getCount());
|
assertEquals(1, tracksCursor.getCount());
|
||||||
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
|
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
|
||||||
assertEquals(10, tracksPointsCursor.getCount());
|
assertEquals(10, tracksPointsCursor.getCount());
|
||||||
Cursor waypointCursor = contentResolver.query(MarkerColumns.CONTENT_URI, null, null, null, MarkerColumns._ID);
|
Cursor markerCursor = contentResolver.query(MarkerColumns.CONTENT_URI, null, null, null, MarkerColumns._ID);
|
||||||
assertEquals(1, waypointCursor.getCount());
|
assertEquals(1, markerCursor.getCount());
|
||||||
// Delete all.
|
// Delete all.
|
||||||
contentProviderUtils.deleteAllTracks(context);
|
contentProviderUtils.deleteAllTracks(context);
|
||||||
// Check whether all have been deleted.
|
// Check whether all have been deleted.
|
||||||
@@ -229,32 +217,32 @@ public class CustomContentProviderUtilsTest {
|
|||||||
assertEquals(0, tracksCursor.getCount());
|
assertEquals(0, tracksCursor.getCount());
|
||||||
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
|
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
|
||||||
assertEquals(0, tracksPointsCursor.getCount());
|
assertEquals(0, tracksPointsCursor.getCount());
|
||||||
waypointCursor = contentResolver.query(MarkerColumns.CONTENT_URI, null, null, null, MarkerColumns._ID);
|
markerCursor = contentResolver.query(MarkerColumns.CONTENT_URI, null, null, null, MarkerColumns._ID);
|
||||||
assertEquals(0, waypointCursor.getCount());
|
assertEquals(0, markerCursor.getCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtils#deleteAllTracks(Context)}
|
* Tests the method {@link ContentProviderUtils#deleteAllTracks(Context)}
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testDeleteAllTracks_withWaypointAndPhoto() throws IOException {
|
public void testDeleteAllTracks_withMarkerAndPhoto() throws IOException {
|
||||||
// Insert track, points and waypoint with photo at first.
|
// Insert track, points and marker with photo at first.
|
||||||
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
||||||
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||||
|
|
||||||
TrackPoint trackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
|
TrackPoint trackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
|
||||||
Marker waypoint = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint.getLocation());
|
Marker marker = TestDataUtil.createMarkerWithPhoto(context, trackId, trackPoint.getLocation());
|
||||||
contentProviderUtils.insertMarker(waypoint);
|
contentProviderUtils.insertMarker(marker);
|
||||||
|
|
||||||
ContentResolver contentResolver = context.getContentResolver();
|
ContentResolver contentResolver = context.getContentResolver();
|
||||||
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
|
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
|
||||||
assertEquals(1, tracksCursor.getCount());
|
assertEquals(1, tracksCursor.getCount());
|
||||||
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
|
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
|
||||||
assertEquals(10, tracksPointsCursor.getCount());
|
assertEquals(10, tracksPointsCursor.getCount());
|
||||||
Cursor waypointCursor = contentResolver.query(MarkerColumns.CONTENT_URI, null, null, null, MarkerColumns._ID);
|
Cursor markerCursor = contentResolver.query(MarkerColumns.CONTENT_URI, null, null, null, MarkerColumns._ID);
|
||||||
assertEquals(1, waypointCursor.getCount());
|
assertEquals(1, markerCursor.getCount());
|
||||||
// Check waypoint has photo and it's in the external storage.
|
// Check marker has photo and it's in the external storage.
|
||||||
assertTrue(waypoint.hasPhoto());
|
assertTrue(marker.hasPhoto());
|
||||||
File dir = FileUtils.getPhotoDir(context, trackId);
|
File dir = FileUtils.getPhotoDir(context, trackId);
|
||||||
assertTrue(dir.isDirectory());
|
assertTrue(dir.isDirectory());
|
||||||
assertEquals(1, dir.list().length);
|
assertEquals(1, dir.list().length);
|
||||||
@@ -266,8 +254,8 @@ public class CustomContentProviderUtilsTest {
|
|||||||
assertEquals(0, tracksCursor.getCount());
|
assertEquals(0, tracksCursor.getCount());
|
||||||
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
|
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
|
||||||
assertEquals(0, tracksPointsCursor.getCount());
|
assertEquals(0, tracksPointsCursor.getCount());
|
||||||
waypointCursor = contentResolver.query(MarkerColumns.CONTENT_URI, null, null, null, MarkerColumns._ID);
|
markerCursor = contentResolver.query(MarkerColumns.CONTENT_URI, null, null, null, MarkerColumns._ID);
|
||||||
assertEquals(0, waypointCursor.getCount());
|
assertEquals(0, markerCursor.getCount());
|
||||||
assertFalse(dir.exists());
|
assertFalse(dir.exists());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,16 +275,16 @@ public class CustomContentProviderUtilsTest {
|
|||||||
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId2, 10);
|
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId2, 10);
|
||||||
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId3, 10);
|
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId3, 10);
|
||||||
|
|
||||||
Marker waypoint = new Marker(trackId1, contentProviderUtils.getLastValidTrackPoint(trackId2));
|
Marker marker = new Marker(trackId1, contentProviderUtils.getLastValidTrackPoint(trackId2));
|
||||||
contentProviderUtils.insertMarker(waypoint);
|
contentProviderUtils.insertMarker(marker);
|
||||||
|
|
||||||
ContentResolver contentResolver = context.getContentResolver();
|
ContentResolver contentResolver = context.getContentResolver();
|
||||||
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
|
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
|
||||||
assertEquals(3, tracksCursor.getCount());
|
assertEquals(3, tracksCursor.getCount());
|
||||||
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
|
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
|
||||||
assertEquals(20, tracksPointsCursor.getCount());
|
assertEquals(20, tracksPointsCursor.getCount());
|
||||||
Cursor waypointCursor = contentResolver.query(MarkerColumns.CONTENT_URI, null, null, null, MarkerColumns._ID);
|
Cursor markerCursor = contentResolver.query(MarkerColumns.CONTENT_URI, null, null, null, MarkerColumns._ID);
|
||||||
assertEquals(1, waypointCursor.getCount());
|
assertEquals(1, markerCursor.getCount());
|
||||||
// Delete one track.
|
// Delete one track.
|
||||||
contentProviderUtils.deleteTrack(context, trackId1);
|
contentProviderUtils.deleteTrack(context, trackId1);
|
||||||
// Check whether all data of a track has been deleted.
|
// Check whether all data of a track has been deleted.
|
||||||
@@ -304,15 +292,15 @@ public class CustomContentProviderUtilsTest {
|
|||||||
assertEquals(2, tracksCursor.getCount());
|
assertEquals(2, tracksCursor.getCount());
|
||||||
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
|
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
|
||||||
assertEquals(20, tracksPointsCursor.getCount());
|
assertEquals(20, tracksPointsCursor.getCount());
|
||||||
waypointCursor = contentResolver.query(MarkerColumns.CONTENT_URI, null, null, null, MarkerColumns._ID);
|
markerCursor = contentResolver.query(MarkerColumns.CONTENT_URI, null, null, null, MarkerColumns._ID);
|
||||||
assertEquals(0, waypointCursor.getCount());
|
assertEquals(0, markerCursor.getCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtils#deleteTrack(Context, Track.Id)}.
|
* Tests the method {@link ContentProviderUtils#deleteTrack(Context, Track.Id)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testDeleteTrack_withWaypointPhoto() throws IOException {
|
public void testDeleteTrack_withMarkerPhoto() throws IOException {
|
||||||
// Insert three tracks.
|
// Insert three tracks.
|
||||||
long random = System.currentTimeMillis();
|
long random = System.currentTimeMillis();
|
||||||
Track.Id trackId1 = new Track.Id(random);
|
Track.Id trackId1 = new Track.Id(random);
|
||||||
@@ -323,15 +311,15 @@ public class CustomContentProviderUtilsTest {
|
|||||||
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId2, 10);
|
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId2, 10);
|
||||||
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId3, 10);
|
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId3, 10);
|
||||||
|
|
||||||
// Insert a waypoint in tracks trackId and trackId + 1.
|
// Insert a marker in tracks trackId and trackId + 1.
|
||||||
TrackPoint trackPoint1 = contentProviderUtils.getLastValidTrackPoint(trackId1);
|
TrackPoint trackPoint1 = contentProviderUtils.getLastValidTrackPoint(trackId1);
|
||||||
Marker waypoint1 = TestDataUtil.createWaypointWithPhoto(context, trackId1, trackPoint1.getLocation());
|
Marker marker1 = TestDataUtil.createMarkerWithPhoto(context, trackId1, trackPoint1.getLocation());
|
||||||
contentProviderUtils.insertMarker(waypoint1);
|
contentProviderUtils.insertMarker(marker1);
|
||||||
File dir1 = FileUtils.getPhotoDir(context, trackId1);
|
File dir1 = FileUtils.getPhotoDir(context, trackId1);
|
||||||
|
|
||||||
TrackPoint trackPoint2 = contentProviderUtils.getLastValidTrackPoint(trackId2);
|
TrackPoint trackPoint2 = contentProviderUtils.getLastValidTrackPoint(trackId2);
|
||||||
Marker waypoint2 = TestDataUtil.createWaypointWithPhoto(context, trackId2, trackPoint2.getLocation());
|
Marker marker2 = TestDataUtil.createMarkerWithPhoto(context, trackId2, trackPoint2.getLocation());
|
||||||
contentProviderUtils.insertMarker(waypoint2);
|
contentProviderUtils.insertMarker(marker2);
|
||||||
File dir2 = FileUtils.getPhotoDir(context, trackId2);
|
File dir2 = FileUtils.getPhotoDir(context, trackId2);
|
||||||
|
|
||||||
// Check.
|
// Check.
|
||||||
@@ -340,9 +328,9 @@ public class CustomContentProviderUtilsTest {
|
|||||||
assertEquals(3, tracksCursor.getCount());
|
assertEquals(3, tracksCursor.getCount());
|
||||||
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
|
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
|
||||||
assertEquals(30, tracksPointsCursor.getCount());
|
assertEquals(30, tracksPointsCursor.getCount());
|
||||||
Cursor waypointCursor = contentResolver.query(MarkerColumns.CONTENT_URI, null, null, null, MarkerColumns._ID);
|
Cursor markerCursor = contentResolver.query(MarkerColumns.CONTENT_URI, null, null, null, MarkerColumns._ID);
|
||||||
assertEquals(2, waypointCursor.getCount());
|
assertEquals(2, markerCursor.getCount());
|
||||||
assertTrue(waypoint1.hasPhoto());
|
assertTrue(marker1.hasPhoto());
|
||||||
assertTrue(dir1.isDirectory());
|
assertTrue(dir1.isDirectory());
|
||||||
assertEquals(1, dir1.list().length);
|
assertEquals(1, dir1.list().length);
|
||||||
assertTrue(dir1.exists());
|
assertTrue(dir1.exists());
|
||||||
@@ -356,8 +344,8 @@ public class CustomContentProviderUtilsTest {
|
|||||||
assertEquals(2, tracksCursor.getCount());
|
assertEquals(2, tracksCursor.getCount());
|
||||||
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
|
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
|
||||||
assertEquals(20, tracksPointsCursor.getCount());
|
assertEquals(20, tracksPointsCursor.getCount());
|
||||||
waypointCursor = contentResolver.query(MarkerColumns.CONTENT_URI, null, null, null, MarkerColumns._ID);
|
markerCursor = contentResolver.query(MarkerColumns.CONTENT_URI, null, null, null, MarkerColumns._ID);
|
||||||
assertEquals(1, waypointCursor.getCount());
|
assertEquals(1, markerCursor.getCount());
|
||||||
assertFalse(dir1.exists());
|
assertFalse(dir1.exists());
|
||||||
assertTrue(dir2.exists());
|
assertTrue(dir2.exists());
|
||||||
}
|
}
|
||||||
@@ -380,16 +368,6 @@ public class CustomContentProviderUtilsTest {
|
|||||||
assertEquals(trackId, allTracks.get(allTracks.size() - 1).getId());
|
assertEquals(trackId, allTracks.get(allTracks.size() - 1).getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests the method {@link ContentProviderUtils#getLastTrack()}
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testGetLastTrack() {
|
|
||||||
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
|
||||||
contentProviderUtils.insertTrack(TestDataUtil.createTrack(trackId));
|
|
||||||
assertEquals(trackId, contentProviderUtils.getLastTrack().getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtils#getTrack(Track.Id)}
|
* Tests the method {@link ContentProviderUtils#getTrack(Track.Id)}
|
||||||
*/
|
*/
|
||||||
@@ -441,9 +419,9 @@ public class CustomContentProviderUtilsTest {
|
|||||||
* Tests the method {@link ContentProviderUtils#createContentValues(Marker)}.
|
* Tests the method {@link ContentProviderUtils#createContentValues(Marker)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testCreateContentValues_waypoint() {
|
public void testCreateContentValues_marker() {
|
||||||
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
||||||
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
|
Pair<Track, List<TrackPoint>> track = TestDataUtil.createTrack(trackId, 10);
|
||||||
|
|
||||||
// Bottom
|
// Bottom
|
||||||
long startTime = 1000L;
|
long startTime = 1000L;
|
||||||
@@ -462,16 +440,16 @@ public class CustomContentProviderUtilsTest {
|
|||||||
track.first.setTrackStatistics(statistics);
|
track.first.setTrackStatistics(statistics);
|
||||||
contentProviderUtils.insertTrack(track.first);
|
contentProviderUtils.insertTrack(track.first);
|
||||||
|
|
||||||
Marker waypoint = new Marker(trackId, track.second[0]);
|
Marker marker = new Marker(trackId, track.second.get(0));
|
||||||
waypoint.setDescription(TEST_DESC);
|
marker.setDescription(TEST_DESC);
|
||||||
contentProviderUtils.insertMarker(waypoint);
|
contentProviderUtils.insertMarker(marker);
|
||||||
|
|
||||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(contentResolverMock);
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(contentResolverMock);
|
||||||
|
|
||||||
Marker.Id waypointId = new Marker.Id(System.currentTimeMillis());
|
Marker.Id markerId = new Marker.Id(System.currentTimeMillis());
|
||||||
waypoint.setId(waypointId);
|
marker.setId(markerId);
|
||||||
ContentValues contentValues = contentProviderUtils.createContentValues(waypoint);
|
ContentValues contentValues = contentProviderUtils.createContentValues(marker);
|
||||||
assertEquals(waypointId.getId(), contentValues.get(MarkerColumns._ID));
|
assertEquals(markerId.getId(), contentValues.get(MarkerColumns._ID));
|
||||||
assertEquals((int) (TestDataUtil.INITIAL_LONGITUDE * 1000000), contentValues.get(MarkerColumns.LONGITUDE));
|
assertEquals((int) (TestDataUtil.INITIAL_LONGITUDE * 1000000), contentValues.get(MarkerColumns.LONGITUDE));
|
||||||
assertEquals(TEST_DESC, contentValues.get(MarkerColumns.DESCRIPTION));
|
assertEquals(TEST_DESC, contentValues.get(MarkerColumns.DESCRIPTION));
|
||||||
}
|
}
|
||||||
@@ -480,7 +458,7 @@ public class CustomContentProviderUtilsTest {
|
|||||||
* Tests the method {@link ContentProviderUtils#createMarker(Cursor)}.
|
* Tests the method {@link ContentProviderUtils#createMarker(Cursor)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testCreateWaypoint() {
|
public void testCreateMarker() {
|
||||||
int startColumnIndex = 1;
|
int startColumnIndex = 1;
|
||||||
int columnIndex = startColumnIndex;
|
int columnIndex = startColumnIndex;
|
||||||
when(cursorMock.getColumnIndexOrThrow(MarkerColumns._ID)).thenReturn(columnIndex++);
|
when(cursorMock.getColumnIndexOrThrow(MarkerColumns._ID)).thenReturn(columnIndex++);
|
||||||
@@ -504,80 +482,80 @@ public class CustomContentProviderUtilsTest {
|
|||||||
long trackId = 11L;
|
long trackId = 11L;
|
||||||
when(cursorMock.getLong(columnIndex++)).thenReturn(trackId);
|
when(cursorMock.getLong(columnIndex++)).thenReturn(trackId);
|
||||||
|
|
||||||
Marker waypoint = contentProviderUtils.createMarker(cursorMock);
|
Marker marker = contentProviderUtils.createMarker(cursorMock);
|
||||||
assertEquals(id, waypoint.getId().getId());
|
assertEquals(id, marker.getId().getId());
|
||||||
assertEquals(name, waypoint.getName());
|
assertEquals(name, marker.getName());
|
||||||
assertEquals(trackId, waypoint.getTrackId().getId());
|
assertEquals(trackId, marker.getTrackId().getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method
|
* Tests the method
|
||||||
* {@link ContentProviderUtils#deleteMarker(Context, Marker.Id)}
|
* {@link ContentProviderUtils#deleteMarker(Context, Marker.Id)}
|
||||||
* when there is only one waypoint in the track.
|
* when there is only one marker in the track.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testDeleteWaypoint_onlyOneWayPoint() {
|
public void testDeleteMarker_onlyOneMarker() {
|
||||||
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
||||||
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||||
|
|
||||||
// Insert at first.
|
// Insert at first.
|
||||||
Marker waypoint1 = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId));
|
Marker marker1 = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId));
|
||||||
waypoint1.setDescription(TEST_DESC);
|
marker1.setDescription(TEST_DESC);
|
||||||
contentProviderUtils.insertMarker(waypoint1);
|
contentProviderUtils.insertMarker(marker1);
|
||||||
|
|
||||||
// Check insert was done.
|
// Check insert was done.
|
||||||
assertEquals(contentProviderUtils.getMarkerCount(trackId), 1);
|
assertEquals(contentProviderUtils.getMarkerCount(trackId), 1);
|
||||||
|
|
||||||
// Get waypoint id that needs to delete.
|
// Get marker id that needs to delete.
|
||||||
Marker.Id waypoint1Id = new Marker.Id(ContentUris.parseId(contentProviderUtils.insertMarker(waypoint1)));
|
Marker.Id marker1Id = new Marker.Id(ContentUris.parseId(contentProviderUtils.insertMarker(marker1)));
|
||||||
|
|
||||||
// Delete
|
// Delete
|
||||||
contentProviderUtils.deleteMarker(context, waypoint1Id);
|
contentProviderUtils.deleteMarker(context, marker1Id);
|
||||||
|
|
||||||
assertNull(contentProviderUtils.getMarker(waypoint1Id));
|
assertNull(contentProviderUtils.getMarker(marker1Id));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method
|
* Tests the method
|
||||||
* {@link ContentProviderUtils#deleteMarker(Context, Marker.Id)}
|
* {@link ContentProviderUtils#deleteMarker(Context, Marker.Id)}
|
||||||
* when there is only one waypoint in the track.
|
* when there is only one marker in the track.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testDeleteWaypoint_onlyOneWayPointWithPhotoUrl() throws IOException {
|
public void testDeleteMarker_onlyOneMarkerWithPhotoUrl() throws IOException {
|
||||||
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
||||||
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||||
|
|
||||||
// Insert at first.
|
// Insert at first.
|
||||||
TrackPoint trackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
|
TrackPoint trackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
|
||||||
Marker waypoint1 = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint.getLocation());
|
Marker marker1 = TestDataUtil.createMarkerWithPhoto(context, trackId, trackPoint.getLocation());
|
||||||
contentProviderUtils.insertMarker(waypoint1);
|
contentProviderUtils.insertMarker(marker1);
|
||||||
|
|
||||||
// Check insert was done.
|
// Check insert was done.
|
||||||
assertEquals(contentProviderUtils.getMarkerCount(trackId), 1);
|
assertEquals(contentProviderUtils.getMarkerCount(trackId), 1);
|
||||||
|
|
||||||
// Get waypoint id that needs to delete.
|
// Get marker id that needs to delete.
|
||||||
Marker.Id waypoint1Id = new Marker.Id(ContentUris.parseId(contentProviderUtils.insertMarker(waypoint1)));
|
Marker.Id marker1Id = new Marker.Id(ContentUris.parseId(contentProviderUtils.insertMarker(marker1)));
|
||||||
|
|
||||||
// Check waypoint has photo and it's in the external storage.
|
// Check marker has photo and it's in the external storage.
|
||||||
assertTrue(waypoint1.hasPhoto());
|
assertTrue(marker1.hasPhoto());
|
||||||
File dir = FileUtils.getPhotoDir(context, trackId);
|
File dir = FileUtils.getPhotoDir(context, trackId);
|
||||||
assertTrue(dir.isDirectory());
|
assertTrue(dir.isDirectory());
|
||||||
assertEquals(1, dir.list().length);
|
assertEquals(1, dir.list().length);
|
||||||
assertTrue(dir.exists());
|
assertTrue(dir.exists());
|
||||||
|
|
||||||
// Delete
|
// Delete
|
||||||
contentProviderUtils.deleteMarker(context, waypoint1Id);
|
contentProviderUtils.deleteMarker(context, marker1Id);
|
||||||
|
|
||||||
// Check waypoint doesn't exists and photo folder was deleted.
|
// Check marker doesn't exists and photo folder was deleted.
|
||||||
assertNull(contentProviderUtils.getMarker(waypoint1Id));
|
assertNull(contentProviderUtils.getMarker(marker1Id));
|
||||||
assertFalse(dir.exists());
|
assertFalse(dir.exists());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtils#deleteMarker(Context, Marker.Id)} when there is more than one waypoint in the track.
|
* Tests the method {@link ContentProviderUtils#deleteMarker(Context, Marker.Id)} when there is more than one marker in the track.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testDeleteWaypoint_hasNextWayPoint() {
|
public void testDeleteMarker_hasNextMarker() {
|
||||||
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
||||||
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||||
|
|
||||||
@@ -598,38 +576,38 @@ public class CustomContentProviderUtilsTest {
|
|||||||
// TestDataUtil.insertTrackWithLocations(contentProviderUtils, track);
|
// TestDataUtil.insertTrackWithLocations(contentProviderUtils, track);
|
||||||
|
|
||||||
// Insert at first.
|
// Insert at first.
|
||||||
Marker waypoint1 = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId));
|
Marker marker1 = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId));
|
||||||
waypoint1.setDescription(MOCK_DESC);
|
marker1.setDescription(MOCK_DESC);
|
||||||
Marker.Id waypoint1Id = new Marker.Id(ContentUris.parseId(contentProviderUtils.insertMarker(waypoint1)));
|
Marker.Id marker1Id = new Marker.Id(ContentUris.parseId(contentProviderUtils.insertMarker(marker1)));
|
||||||
|
|
||||||
Marker waypoint2 = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId));
|
Marker marker2 = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId));
|
||||||
waypoint2.setDescription(MOCK_DESC);
|
marker2.setDescription(MOCK_DESC);
|
||||||
Marker.Id waypoint2Id = new Marker.Id(ContentUris.parseId(contentProviderUtils.insertMarker(waypoint2)));
|
Marker.Id marker2Id = new Marker.Id(ContentUris.parseId(contentProviderUtils.insertMarker(marker2)));
|
||||||
|
|
||||||
// Delete
|
// Delete
|
||||||
assertNotNull(contentProviderUtils.getMarker(waypoint1Id));
|
assertNotNull(contentProviderUtils.getMarker(marker1Id));
|
||||||
contentProviderUtils.deleteMarker(context, waypoint1Id);
|
contentProviderUtils.deleteMarker(context, marker1Id);
|
||||||
assertNull(contentProviderUtils.getMarker(waypoint1Id));
|
assertNull(contentProviderUtils.getMarker(marker1Id));
|
||||||
|
|
||||||
assertEquals(MOCK_DESC, contentProviderUtils.getMarker(waypoint2Id).getDescription());
|
assertEquals(MOCK_DESC, contentProviderUtils.getMarker(marker2Id).getDescription());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtils#getNextMarkerNumber(Track.Id)}.
|
* Tests the method {@link ContentProviderUtils#getNextMarkerNumber(Track.Id)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetNextWaypointNumber() {
|
public void testGetNextMarkerNumber() {
|
||||||
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
||||||
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||||
|
|
||||||
Marker waypoint1 = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId));
|
Marker marker1 = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId));
|
||||||
Marker waypoint2 = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId));
|
Marker marker2 = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId));
|
||||||
Marker waypoint3 = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId));
|
Marker marker3 = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId));
|
||||||
Marker waypoint4 = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId));
|
Marker marker4 = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId));
|
||||||
contentProviderUtils.insertMarker(waypoint1);
|
contentProviderUtils.insertMarker(marker1);
|
||||||
contentProviderUtils.insertMarker(waypoint2);
|
contentProviderUtils.insertMarker(marker2);
|
||||||
contentProviderUtils.insertMarker(waypoint3);
|
contentProviderUtils.insertMarker(marker3);
|
||||||
contentProviderUtils.insertMarker(waypoint4);
|
contentProviderUtils.insertMarker(marker4);
|
||||||
|
|
||||||
assertEquals(4, contentProviderUtils.getNextMarkerNumber(trackId));
|
assertEquals(4, contentProviderUtils.getNextMarkerNumber(trackId));
|
||||||
}
|
}
|
||||||
@@ -639,53 +617,53 @@ public class CustomContentProviderUtilsTest {
|
|||||||
* {@link ContentProviderUtils#getMarker(Marker.Id)}.
|
* {@link ContentProviderUtils#getMarker(Marker.Id)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testInsertAndGetWaypoint() {
|
public void testInsertAndGetMarker() {
|
||||||
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
||||||
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||||
|
|
||||||
Marker waypoint = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId));
|
Marker marker = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId));
|
||||||
waypoint.setDescription(TEST_DESC);
|
marker.setDescription(TEST_DESC);
|
||||||
Marker.Id waypointId = new Marker.Id(ContentUris.parseId(contentProviderUtils.insertMarker(waypoint)));
|
Marker.Id markerId = new Marker.Id(ContentUris.parseId(contentProviderUtils.insertMarker(marker)));
|
||||||
|
|
||||||
assertEquals(TEST_DESC, contentProviderUtils.getMarker(waypointId).getDescription());
|
assertEquals(TEST_DESC, contentProviderUtils.getMarker(markerId).getDescription());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtils#updateMarker(Context, Marker)}.
|
* Tests the method {@link ContentProviderUtils#updateMarker(Context, Marker)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testUpdateWaypoint() {
|
public void testUpdateMarker() {
|
||||||
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
||||||
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||||
|
|
||||||
// Insert at first.
|
// Insert at first.
|
||||||
Marker waypoint = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId));
|
Marker marker = new Marker(trackId, contentProviderUtils.getLastValidTrackPoint(trackId));
|
||||||
waypoint.setDescription(TEST_DESC);
|
marker.setDescription(TEST_DESC);
|
||||||
Marker.Id waypointId = new Marker.Id(ContentUris.parseId(contentProviderUtils.insertMarker(waypoint)));
|
Marker.Id markerId = new Marker.Id(ContentUris.parseId(contentProviderUtils.insertMarker(marker)));
|
||||||
|
|
||||||
// Update
|
// Update
|
||||||
waypoint = contentProviderUtils.getMarker(waypointId);
|
marker = contentProviderUtils.getMarker(markerId);
|
||||||
waypoint.setDescription(TEST_DESC_NEW);
|
marker.setDescription(TEST_DESC_NEW);
|
||||||
contentProviderUtils.updateMarker(context, waypoint);
|
contentProviderUtils.updateMarker(context, marker);
|
||||||
|
|
||||||
assertEquals(TEST_DESC_NEW, contentProviderUtils.getMarker(waypointId).getDescription());
|
assertEquals(TEST_DESC_NEW, contentProviderUtils.getMarker(markerId).getDescription());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtils#updateMarker(Context, Marker)}.
|
* Tests the method {@link ContentProviderUtils#updateMarker(Context, Marker)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testUpdateWaypoint_withPhoto() throws IOException {
|
public void testUpdateMarker_withPhoto() throws IOException {
|
||||||
// tests after update waypoint with photo the photo remains in the storage.
|
// tests after update marker with photo the photo remains in the storage.
|
||||||
|
|
||||||
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
||||||
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||||
|
|
||||||
// Insert at first.
|
// Insert at first.
|
||||||
TrackPoint trackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
|
TrackPoint trackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
|
||||||
Marker waypoint = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint.getLocation());
|
Marker marker = TestDataUtil.createMarkerWithPhoto(context, trackId, trackPoint.getLocation());
|
||||||
waypoint.setDescription(TEST_DESC);
|
marker.setDescription(TEST_DESC);
|
||||||
Marker.Id waypointId = new Marker.Id(ContentUris.parseId(contentProviderUtils.insertMarker(waypoint)));
|
Marker.Id markerId = new Marker.Id(ContentUris.parseId(contentProviderUtils.insertMarker(marker)));
|
||||||
|
|
||||||
File dir = new File(FileUtils.getPhotoDir(context), "" + trackId.getId());
|
File dir = new File(FileUtils.getPhotoDir(context), "" + trackId.getId());
|
||||||
assertTrue(dir.exists());
|
assertTrue(dir.exists());
|
||||||
@@ -693,14 +671,14 @@ public class CustomContentProviderUtilsTest {
|
|||||||
assertEquals(1, dir.list().length);
|
assertEquals(1, dir.list().length);
|
||||||
|
|
||||||
// Update
|
// Update
|
||||||
waypoint = contentProviderUtils.getMarker(waypointId);
|
marker = contentProviderUtils.getMarker(markerId);
|
||||||
waypoint.setName(TEST_NAME_NEW);
|
marker.setName(TEST_NAME_NEW);
|
||||||
waypoint.setDescription(TEST_DESC_NEW);
|
marker.setDescription(TEST_DESC_NEW);
|
||||||
contentProviderUtils.updateMarker(context, waypoint);
|
contentProviderUtils.updateMarker(context, marker);
|
||||||
|
|
||||||
assertEquals(TEST_NAME_NEW, contentProviderUtils.getMarker(waypointId).getName());
|
assertEquals(TEST_NAME_NEW, contentProviderUtils.getMarker(markerId).getName());
|
||||||
assertEquals(TEST_DESC_NEW, contentProviderUtils.getMarker(waypointId).getDescription());
|
assertEquals(TEST_DESC_NEW, contentProviderUtils.getMarker(markerId).getDescription());
|
||||||
assertTrue(waypoint.hasPhoto());
|
assertTrue(marker.hasPhoto());
|
||||||
assertTrue(dir.exists());
|
assertTrue(dir.exists());
|
||||||
assertTrue(dir.isDirectory());
|
assertTrue(dir.isDirectory());
|
||||||
assertEquals(1, dir.list().length);
|
assertEquals(1, dir.list().length);
|
||||||
@@ -710,17 +688,17 @@ public class CustomContentProviderUtilsTest {
|
|||||||
* Tests the method {@link ContentProviderUtils#updateMarker(Context, Marker)}.
|
* Tests the method {@link ContentProviderUtils#updateMarker(Context, Marker)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testUpdateWaypoint_delPhotoAndDir() throws IOException {
|
public void testUpdateMarker_delPhotoAndDir() throws IOException {
|
||||||
// tests after update waypoint if user deletes the photo then file photo is deleted from the storage. Also empty directory is deleted.
|
// tests after update marker if user deletes the photo then file photo is deleted from the storage. Also empty directory is deleted.
|
||||||
|
|
||||||
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
||||||
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||||
|
|
||||||
// Insert at first.
|
// Insert at first.
|
||||||
TrackPoint trackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
|
TrackPoint trackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
|
||||||
Marker waypoint = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint.getLocation());
|
Marker marker = TestDataUtil.createMarkerWithPhoto(context, trackId, trackPoint.getLocation());
|
||||||
waypoint.setDescription(TEST_DESC);
|
marker.setDescription(TEST_DESC);
|
||||||
Marker.Id waypointId = new Marker.Id(ContentUris.parseId(contentProviderUtils.insertMarker(waypoint)));
|
Marker.Id markerId = new Marker.Id(ContentUris.parseId(contentProviderUtils.insertMarker(marker)));
|
||||||
|
|
||||||
File dir = new File(FileUtils.getPhotoDir(context), "" + trackId.getId());
|
File dir = new File(FileUtils.getPhotoDir(context), "" + trackId.getId());
|
||||||
assertTrue(dir.exists());
|
assertTrue(dir.exists());
|
||||||
@@ -728,15 +706,15 @@ public class CustomContentProviderUtilsTest {
|
|||||||
assertEquals(1, dir.list().length);
|
assertEquals(1, dir.list().length);
|
||||||
|
|
||||||
// Update
|
// Update
|
||||||
waypoint = contentProviderUtils.getMarker(waypointId);
|
marker = contentProviderUtils.getMarker(markerId);
|
||||||
waypoint.setName(TEST_NAME_NEW);
|
marker.setName(TEST_NAME_NEW);
|
||||||
waypoint.setDescription(TEST_DESC_NEW);
|
marker.setDescription(TEST_DESC_NEW);
|
||||||
waypoint.setPhotoUrl(null);
|
marker.setPhotoUrl(null);
|
||||||
contentProviderUtils.updateMarker(context, waypoint);
|
contentProviderUtils.updateMarker(context, marker);
|
||||||
|
|
||||||
assertEquals(TEST_NAME_NEW, contentProviderUtils.getMarker(waypointId).getName());
|
assertEquals(TEST_NAME_NEW, contentProviderUtils.getMarker(markerId).getName());
|
||||||
assertEquals(TEST_DESC_NEW, contentProviderUtils.getMarker(waypointId).getDescription());
|
assertEquals(TEST_DESC_NEW, contentProviderUtils.getMarker(markerId).getDescription());
|
||||||
assertFalse(waypoint.hasPhoto());
|
assertFalse(marker.hasPhoto());
|
||||||
assertFalse(dir.exists());
|
assertFalse(dir.exists());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -744,53 +722,53 @@ public class CustomContentProviderUtilsTest {
|
|||||||
* Tests the method {@link ContentProviderUtils#updateMarker(Context, Marker)}.
|
* Tests the method {@link ContentProviderUtils#updateMarker(Context, Marker)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testUpdateWaypoint_delPhotoNotDir() throws IOException {
|
public void testUpdateMarker_delPhotoNotDir() throws IOException {
|
||||||
// tests after update waypoint if user deletes the photo then file photo is deleted from the storage. Directory remains if there are more photos from other waypoints.
|
// tests after update marker if user deletes the photo then file photo is deleted from the storage. Directory remains if there are more photos from other markers.
|
||||||
|
|
||||||
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
||||||
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||||
|
|
||||||
// Insert two waypoints with photos.
|
// Insert two markers with photos.
|
||||||
TrackPoint trackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
|
TrackPoint trackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
|
||||||
Marker waypoint = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint.getLocation());
|
Marker marker = TestDataUtil.createMarkerWithPhoto(context, trackId, trackPoint.getLocation());
|
||||||
waypoint.setDescription(TEST_DESC);
|
marker.setDescription(TEST_DESC);
|
||||||
Marker otherWaypoint = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint.getLocation());
|
Marker otherMarker = TestDataUtil.createMarkerWithPhoto(context, trackId, trackPoint.getLocation());
|
||||||
otherWaypoint.setDescription(TEST_DESC);
|
otherMarker.setDescription(TEST_DESC);
|
||||||
Marker.Id waypointId = new Marker.Id(ContentUris.parseId(contentProviderUtils.insertMarker(waypoint)));
|
Marker.Id markerId = new Marker.Id(ContentUris.parseId(contentProviderUtils.insertMarker(marker)));
|
||||||
contentProviderUtils.insertMarker(otherWaypoint);
|
contentProviderUtils.insertMarker(otherMarker);
|
||||||
|
|
||||||
File dir = new File(FileUtils.getPhotoDir(context), "" + trackId.getId());
|
File dir = new File(FileUtils.getPhotoDir(context), "" + trackId.getId());
|
||||||
assertTrue(dir.exists());
|
assertTrue(dir.exists());
|
||||||
assertTrue(dir.isDirectory());
|
assertTrue(dir.isDirectory());
|
||||||
assertEquals(2, dir.list().length);
|
assertEquals(2, dir.list().length);
|
||||||
|
|
||||||
// Update one waypoint deleting photo.
|
// Update one marker deleting photo.
|
||||||
waypoint = contentProviderUtils.getMarker(waypointId);
|
marker = contentProviderUtils.getMarker(markerId);
|
||||||
waypoint.setPhotoUrl(null);
|
marker.setPhotoUrl(null);
|
||||||
contentProviderUtils.updateMarker(context, waypoint);
|
contentProviderUtils.updateMarker(context, marker);
|
||||||
|
|
||||||
assertEquals(TEST_DESC, contentProviderUtils.getMarker(waypointId).getDescription());
|
assertEquals(TEST_DESC, contentProviderUtils.getMarker(markerId).getDescription());
|
||||||
assertFalse(waypoint.hasPhoto());
|
assertFalse(marker.hasPhoto());
|
||||||
assertTrue(dir.exists());
|
assertTrue(dir.exists());
|
||||||
assertTrue(dir.isDirectory());
|
assertTrue(dir.isDirectory());
|
||||||
assertEquals(1, dir.list().length);
|
assertEquals(1, dir.list().length);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtils#bulkInsertTrackPoint(TrackPoint[], Track.Id)}.
|
* Tests the method {@link ContentProviderUtils#bulkInsertTrackPoint(List, Track.Id)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testBulkInsertTrackPoint() {
|
public void testBulkInsertTrackPoint() {
|
||||||
// given
|
// given
|
||||||
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
||||||
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
|
Pair<Track, List<TrackPoint>> track = TestDataUtil.createTrack(trackId, 10);
|
||||||
TestDataUtil.insertTrackWithLocations(contentProviderUtils, track.first, track.second);
|
TestDataUtil.insertTrackWithLocations(contentProviderUtils, track.first, track.second);
|
||||||
|
|
||||||
// when / then
|
// when / then
|
||||||
contentProviderUtils.bulkInsertTrackPoint(track.second, trackId);
|
contentProviderUtils.bulkInsertTrackPoint(track.second, trackId);
|
||||||
assertEquals(20, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
|
assertEquals(20, contentProviderUtils.getTrackPointCursor(trackId, null, 1000).getCount());
|
||||||
contentProviderUtils.bulkInsertTrackPoint(Arrays.copyOfRange(track.second, 0, 8), trackId);
|
contentProviderUtils.bulkInsertTrackPoint(track.second.subList(0, 8), trackId);
|
||||||
assertEquals(28, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
|
assertEquals(28, contentProviderUtils.getTrackPointCursor(trackId, null, 1000).getCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -885,104 +863,45 @@ public class CustomContentProviderUtilsTest {
|
|||||||
checkLocation(9, lastTrackPoint.getLocation());
|
checkLocation(9, lastTrackPoint.getLocation());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests the method {@link ContentProviderUtils#getTrackPointCursor(Track.Id, long, int, boolean)} in descending.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testGetTrackPointCursor_desc() {
|
|
||||||
// given
|
|
||||||
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
|
||||||
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
|
|
||||||
contentProviderUtils.insertTrack(track.first);
|
|
||||||
|
|
||||||
long[] trackpointIds = new long[track.second.length];
|
|
||||||
for (int i = 0; i < trackpointIds.length; i++) {
|
|
||||||
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.second[i], track.first.getId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// when
|
|
||||||
Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds[1], 5, true);
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertEquals(2, cursor.getCount());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests the method {@link ContentProviderUtils#getTrackPointCursor(Track.Id, long, int, boolean)} in ascending.
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetTrackPointCursor_asc() {
|
public void testGetTrackPointCursor_asc() {
|
||||||
// given
|
// given
|
||||||
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
||||||
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
|
Pair<Track, List<TrackPoint>> track = TestDataUtil.createTrack(trackId, 10);
|
||||||
contentProviderUtils.insertTrack(track.first);
|
contentProviderUtils.insertTrack(track.first);
|
||||||
|
|
||||||
long[] trackpointIds = new long[track.second.length];
|
List<TrackPoint.Id> trackpointIds = track.second.stream()
|
||||||
for (int i = 0; i < trackpointIds.length; i++) {
|
.map(it -> ContentUris.parseId(contentProviderUtils.insertTrackPoint(it, track.first.getId())))
|
||||||
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.second[i], track.first.getId()));
|
.map(TrackPoint.Id::new).collect(Collectors.toList());
|
||||||
}
|
|
||||||
|
|
||||||
// when
|
// when
|
||||||
Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds[8], 5, false);
|
Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds.get(8), 5);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(2, cursor.getCount());
|
assertEquals(2, cursor.getCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(Track.Id, long, boolean)} in descending.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testGetTrackPointLocationIterator_desc() {
|
|
||||||
// given
|
|
||||||
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
|
||||||
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
|
|
||||||
contentProviderUtils.insertTrack(track.first);
|
|
||||||
|
|
||||||
long[] trackpointIds = new long[track.second.length];
|
|
||||||
for (int i = 0; i < trackpointIds.length; i++) {
|
|
||||||
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.second[i], track.first.getId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
long startTrackPointId = trackpointIds[9];
|
|
||||||
// when
|
|
||||||
TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, startTrackPointId, true);
|
|
||||||
|
|
||||||
// then
|
|
||||||
for (int i = 0; i < trackpointIds.length; i++) {
|
|
||||||
assertTrue(trackPointIterator.hasNext());
|
|
||||||
TrackPoint trackPoint = trackPointIterator.next();
|
|
||||||
assertEquals(startTrackPointId - i, trackPointIterator.getTrackPointId());
|
|
||||||
checkLocation((trackpointIds.length - 1) - i, trackPoint.getLocation());
|
|
||||||
}
|
|
||||||
assertFalse(trackPointIterator.hasNext());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(Track.Id, long, boolean)} in ascending.
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetTrackPointLocationIterator_asc() {
|
public void testGetTrackPointLocationIterator_asc() {
|
||||||
// given
|
// given
|
||||||
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
||||||
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
|
Pair<Track, List<TrackPoint>> track = TestDataUtil.createTrack(trackId, 10);
|
||||||
contentProviderUtils.insertTrack(track.first);
|
contentProviderUtils.insertTrack(track.first);
|
||||||
|
|
||||||
long[] trackpointIds = new long[track.second.length];
|
List<TrackPoint.Id> trackpointIds = track.second.stream()
|
||||||
for (int i = 0; i < trackpointIds.length; i++) {
|
.map(it -> ContentUris.parseId(contentProviderUtils.insertTrackPoint(it, track.first.getId())))
|
||||||
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.second[i], track.first.getId()));
|
.map(TrackPoint.Id::new).collect(Collectors.toList());
|
||||||
}
|
|
||||||
|
|
||||||
long startTrackPointId = trackpointIds[0];
|
TrackPoint.Id startTrackPointId = trackpointIds.get(0);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, startTrackPointId, false);
|
TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, trackpointIds.get(0));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
for (int i = 0; i < trackpointIds.length; i++) {
|
for (int i = 0; i < trackpointIds.size(); i++) {
|
||||||
assertTrue(trackPointIterator.hasNext());
|
assertTrue(trackPointIterator.hasNext());
|
||||||
TrackPoint trackPoint = trackPointIterator.next();
|
TrackPoint trackPoint = trackPointIterator.next();
|
||||||
assertEquals(startTrackPointId + i, trackPointIterator.getTrackPointId());
|
assertEquals(startTrackPointId.getId() + i, trackPoint.getId().getId());
|
||||||
|
|
||||||
checkLocation(i, trackPoint.getLocation());
|
checkLocation(i, trackPoint.getLocation());
|
||||||
}
|
}
|
||||||
|
|||||||
+98
@@ -5,6 +5,7 @@ import android.database.Cursor;
|
|||||||
import android.database.sqlite.SQLiteConstraintException;
|
import android.database.sqlite.SQLiteConstraintException;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.database.sqlite.SQLiteOpenHelper;
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
|
import android.database.sqlite.SQLiteQueryBuilder;
|
||||||
|
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
@@ -14,7 +15,9 @@ import org.junit.Before;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.data.MarkerColumns;
|
import de.dennisguse.opentracks.content.data.MarkerColumns;
|
||||||
@@ -165,6 +168,101 @@ public class CustomSQLiteOpenHelperTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void upgrade_data_to_30() {
|
||||||
|
// given: a track in version 29
|
||||||
|
createVersion23();
|
||||||
|
try (SQLiteDatabase db29 = new CustomSQLiteOpenHelper(context, DATABASE_NAME, 29).getWritableDatabase()) {
|
||||||
|
db29.beginTransaction();
|
||||||
|
db29.execSQL("INSERT INTO tracks (_id) VALUES (1)");
|
||||||
|
|
||||||
|
// Record -> stop
|
||||||
|
db29.execSQL("INSERT INTO tracks (_id) VALUES (2)");
|
||||||
|
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (2, 2.1, 2.1)");
|
||||||
|
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (2, 2.2, 2.2)");
|
||||||
|
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (2, 2.3, 2.3)");
|
||||||
|
|
||||||
|
// Record -> pause -> stop
|
||||||
|
db29.execSQL("INSERT INTO tracks (_id) VALUES (3)");
|
||||||
|
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (3, 3.1, 3.1)");
|
||||||
|
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (3, 3.2, 3.2)");
|
||||||
|
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (3, 0.0, 100)"); //manual PAUSE
|
||||||
|
|
||||||
|
// Record -> segment marker (distance to previous) -> pause -> resume -> stop
|
||||||
|
db29.execSQL("INSERT INTO tracks (_id) VALUES (4)");
|
||||||
|
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 4.1, 4.1)");
|
||||||
|
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 4.2, 4.2)");
|
||||||
|
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 0.0, 100 * 1E6)"); //SegmentEndMarker; will be deleted
|
||||||
|
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 4.3, 4.3)");
|
||||||
|
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 4.4, 4.4)");
|
||||||
|
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 0.0, 100 * 1E6)"); //manual PAUSE
|
||||||
|
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 0.0, 200 * 1E6)"); //manual RESUME
|
||||||
|
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 4.5, 4.5)");
|
||||||
|
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 4.6, 4.6)");
|
||||||
|
|
||||||
|
db29.setTransactionSuccessful();
|
||||||
|
db29.endTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
// when / then
|
||||||
|
try (SQLiteDatabase db30 = new CustomSQLiteOpenHelper(context, DATABASE_NAME, 30).getWritableDatabase()) {
|
||||||
|
{
|
||||||
|
// Track 1
|
||||||
|
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
|
||||||
|
queryBuilder.setTables("trackpoints");
|
||||||
|
queryBuilder.appendWhere("trackid = 1");
|
||||||
|
try (Cursor cursor = queryBuilder.query(db30, null, null, null, null, null, "_id")) {
|
||||||
|
assertEquals(0, cursor.getCount());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Track 2
|
||||||
|
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
|
||||||
|
queryBuilder.setTables("trackpoints");
|
||||||
|
queryBuilder.appendWhere("trackid = 2");
|
||||||
|
try (Cursor cursor = queryBuilder.query(db30, null, null, null, null, null, "_id")) {
|
||||||
|
assertEquals(3, cursor.getCount());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Track 3
|
||||||
|
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
|
||||||
|
queryBuilder.setTables("trackpoints");
|
||||||
|
queryBuilder.appendWhere("trackid = 3");
|
||||||
|
try (Cursor cursor = queryBuilder.query(db30, null, null, null, null, null, "_id")) {
|
||||||
|
assertEquals(3, cursor.getCount());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Track 4
|
||||||
|
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
|
||||||
|
queryBuilder.setTables("trackpoints");
|
||||||
|
queryBuilder.appendWhere("trackid = 4");
|
||||||
|
try (Cursor cursor = queryBuilder.query(db30, null, null, null, null, null, "_id")) {
|
||||||
|
assertEquals(8, cursor.getCount());
|
||||||
|
|
||||||
|
List<Integer> types = new ArrayList<>();
|
||||||
|
List<Double> latitude = new ArrayList<>();
|
||||||
|
cursor.moveToFirst();
|
||||||
|
do {
|
||||||
|
types.add(cursor.getInt(cursor.getColumnIndexOrThrow("type")));
|
||||||
|
if (!cursor.isNull(cursor.getColumnIndexOrThrow("latitude"))) {
|
||||||
|
latitude.add(cursor.getDouble(cursor.getColumnIndexOrThrow("latitude")));
|
||||||
|
} else {
|
||||||
|
latitude.add(-1.0);
|
||||||
|
}
|
||||||
|
} while (cursor.moveToNext());
|
||||||
|
|
||||||
|
assertEquals(List.of(0, 0, -1, 0, 1, -2, 0, 0), types);
|
||||||
|
assertEquals(List.of(4.1, 4.2, 4.3, 4.4, -1.0, -1.0, 4.5, 4.6), latitude);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void createVersion23() {
|
private void createVersion23() {
|
||||||
// Manually create database schema with version 23 (base version)
|
// Manually create database schema with version 23 (base version)
|
||||||
SQLiteDatabase dbBase = new SQLiteOpenHelper(context, DATABASE_NAME, null, 23) {
|
SQLiteDatabase dbBase = new SQLiteOpenHelper(context, DATABASE_NAME, null, 23) {
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import org.junit.BeforeClass;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.TrackStubUtils;
|
|
||||||
import de.dennisguse.opentracks.chart.ChartPoint;
|
import de.dennisguse.opentracks.chart.ChartPoint;
|
||||||
import de.dennisguse.opentracks.chart.ChartView;
|
import de.dennisguse.opentracks.chart.ChartView;
|
||||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@
|
|||||||
* the License.
|
* the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package de.dennisguse.opentracks;
|
package de.dennisguse.opentracks.fragments;
|
||||||
|
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
|
|
||||||
-131
@@ -1,131 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2012 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations under
|
|
||||||
* the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package de.dennisguse.opentracks.io.file.importer;
|
|
||||||
|
|
||||||
import android.content.ContentUris;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.location.Location;
|
|
||||||
import android.location.LocationManager;
|
|
||||||
import android.net.Uri;
|
|
||||||
|
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
|
||||||
|
|
||||||
import org.mockito.ArgumentCaptor;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.time.ZoneOffset;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.SimpleTimeZone;
|
|
||||||
import java.util.TimeZone;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.data.MarkerColumns;
|
|
||||||
import de.dennisguse.opentracks.content.data.Track;
|
|
||||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
|
||||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.mockito.Mockito.any;
|
|
||||||
import static org.mockito.Mockito.eq;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abstract class for testing file track importers.
|
|
||||||
*
|
|
||||||
* @author Jimmy Shih.
|
|
||||||
*/
|
|
||||||
public abstract class AbstractTestFileTrackImporter {
|
|
||||||
|
|
||||||
static final String TRACK_NAME_0 = "blablub";
|
|
||||||
static final String TRACK_DESCRIPTION_0 = "s'Laebe isch koi Schlotzer";
|
|
||||||
|
|
||||||
static final double TRACK_LATITUDE = 48.768364;
|
|
||||||
static final double TRACK_LONGITUDE = 9.177886;
|
|
||||||
static final double TRACK_ELEVATION = 324.0;
|
|
||||||
|
|
||||||
static final String TRACK_TIME_0 = "2010-04-22T18:21:00Z";
|
|
||||||
static final String TRACK_TIME_1 = "2010-04-22T18:21:50.123Z";
|
|
||||||
static final String TRACK_TIME_2 = "2010-04-22T18:23:00.123Z";
|
|
||||||
static final String TRACK_TIME_3 = "2010-04-22T18:24:50.123Z";
|
|
||||||
|
|
||||||
static final SimpleDateFormat DATE_FORMAT_0 = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'", Locale.US);
|
|
||||||
static final SimpleDateFormat DATE_FORMAT_1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US);
|
|
||||||
|
|
||||||
static final long TRACK_POINT_ID_0 = 1;
|
|
||||||
static final long TRACK_POINT_ID_1 = 2;
|
|
||||||
static final long TRACK_POINT_ID_3 = 4;
|
|
||||||
|
|
||||||
private static final long WAYPOINT_ID_0 = 1;
|
|
||||||
private static final Uri WAYPOINT_ID_O_URI = ContentUris.appendId(MarkerColumns.CONTENT_URI.buildUpon(), WAYPOINT_ID_0).build();
|
|
||||||
|
|
||||||
protected final Context context = ApplicationProvider.getApplicationContext();
|
|
||||||
|
|
||||||
static {
|
|
||||||
// We can't omit the timezones in the test, otherwise it'll use the local timezone and fail depending on where the test runner is.
|
|
||||||
TimeZone utc = SimpleTimeZone.getTimeZone(ZoneOffset.UTC);
|
|
||||||
DATE_FORMAT_0.setTimeZone(utc);
|
|
||||||
DATE_FORMAT_1.setTimeZone(utc);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
public ContentProviderUtils contentProviderUtils;
|
|
||||||
|
|
||||||
TrackPoint createTrackPoint(int index, long time) {
|
|
||||||
Location trackPoint = new Location(LocationManager.GPS_PROVIDER);
|
|
||||||
trackPoint.setLatitude(TRACK_LATITUDE + index);
|
|
||||||
trackPoint.setLongitude(TRACK_LONGITUDE + index);
|
|
||||||
trackPoint.setAltitude(TRACK_ELEVATION + index);
|
|
||||||
trackPoint.setTime(time);
|
|
||||||
return new TrackPoint(trackPoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expects the first track point to be added.
|
|
||||||
*
|
|
||||||
* @param trackPoint the trackPoint
|
|
||||||
* @param trackId the track id
|
|
||||||
* @param trackPointId the track point id
|
|
||||||
*/
|
|
||||||
protected void expectFirstTrackPoint(TrackPoint trackPoint, Track.Id trackId, long trackPointId) {
|
|
||||||
when(contentProviderUtils.bulkInsertTrackPoint(trackPoint != null ? (TrackPoint[]) any() : (TrackPoint[]) any(), eq(trackId))).thenReturn(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expects the track to be updated.
|
|
||||||
*
|
|
||||||
* @param trackCaptor the track
|
|
||||||
* @param lastTrack true if it is the last track in the gpx
|
|
||||||
* @param trackId the track id
|
|
||||||
*/
|
|
||||||
protected void expectTrackUpdate(ArgumentCaptor<Track> trackCaptor, boolean lastTrack, Track.Id trackId) {
|
|
||||||
contentProviderUtils.updateTrack(trackCaptor.capture());
|
|
||||||
|
|
||||||
when(contentProviderUtils.insertMarker(any())).thenReturn(WAYPOINT_ID_O_URI);
|
|
||||||
if (lastTrack) {
|
|
||||||
// Return null to not add waypoints
|
|
||||||
when(contentProviderUtils.getTrack(trackId)).thenReturn(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void verifyTrack(Track track, String name, String description, long time) {
|
|
||||||
assertEquals(name, track.getName());
|
|
||||||
assertEquals(description, track.getDescription());
|
|
||||||
if (time != -1L) {
|
|
||||||
assertEquals(time, track.getTrackStatistics().getStartTime_ms());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+10
-8
@@ -18,7 +18,6 @@ import java.io.ByteArrayInputStream;
|
|||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
@@ -62,7 +61,7 @@ public class ExportImportTest {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
|
Pair<Track, List<TrackPoint>> track = TestDataUtil.createTrack(trackId, 10);
|
||||||
track.first.setIcon(TRACK_ICON);
|
track.first.setIcon(TRACK_ICON);
|
||||||
track.first.setCategory(TRACK_CATEGORY);
|
track.first.setCategory(TRACK_CATEGORY);
|
||||||
track.first.setDescription(TRACK_DESCRIPTION);
|
track.first.setDescription(TRACK_DESCRIPTION);
|
||||||
@@ -70,10 +69,10 @@ public class ExportImportTest {
|
|||||||
contentProviderUtils.bulkInsertTrackPoint(track.second, track.first.getId());
|
contentProviderUtils.bulkInsertTrackPoint(track.second, track.first.getId());
|
||||||
|
|
||||||
trackPoints.clear();
|
trackPoints.clear();
|
||||||
trackPoints.addAll(Arrays.asList(track.second));
|
trackPoints.addAll(track.second);
|
||||||
|
|
||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
Marker marker = new Marker(trackId, track.second[i].getLocation());
|
Marker marker = new Marker(trackId, track.second.get(i).getLocation());
|
||||||
marker.setName("the marker " + i);
|
marker.setName("the marker " + i);
|
||||||
marker.setDescription("the marker description " + i);
|
marker.setDescription("the marker description " + i);
|
||||||
marker.setCategory("the marker category" + i);
|
marker.setCategory("the marker category" + i);
|
||||||
@@ -135,7 +134,7 @@ public class ExportImportTest {
|
|||||||
assertMarkers();
|
assertMarkers();
|
||||||
|
|
||||||
// 3. trackpoints
|
// 3. trackpoints
|
||||||
assertTrackpoints(false, false, false, false);
|
assertTrackpoints(false, false, false, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@@ -170,7 +169,7 @@ public class ExportImportTest {
|
|||||||
assertMarkers();
|
assertMarkers();
|
||||||
|
|
||||||
// 3. trackpoints
|
// 3. trackpoints
|
||||||
assertTrackpoints(true, true, true, true);
|
assertTrackpoints(true, true, true, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@@ -263,7 +262,7 @@ public class ExportImportTest {
|
|||||||
assertMarkers();
|
assertMarkers();
|
||||||
|
|
||||||
// 3. trackpoints
|
// 3. trackpoints
|
||||||
assertTrackpoints(false, true, true, false);
|
assertTrackpoints(false, true, true, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@@ -310,7 +309,7 @@ public class ExportImportTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertTrackpoints(boolean verifyPower, boolean verifyHeartrate, boolean verifyCadence, boolean verifyElevationGain) {
|
private void assertTrackpoints(boolean verifyPower, boolean verifyHeartrate, boolean verifyCadence, boolean verifyElevationGain, boolean verifyElevationLoss) {
|
||||||
List<TrackPoint> importedTrackPoints = contentProviderUtils.getTrackPoints(importTrackId);
|
List<TrackPoint> importedTrackPoints = contentProviderUtils.getTrackPoints(importTrackId);
|
||||||
assertEquals(trackPoints.size(), importedTrackPoints.size());
|
assertEquals(trackPoints.size(), importedTrackPoints.size());
|
||||||
|
|
||||||
@@ -339,6 +338,9 @@ public class ExportImportTest {
|
|||||||
if (verifyElevationGain) {
|
if (verifyElevationGain) {
|
||||||
assertEquals(trackPoint.getElevationGain(), importedTrackPoint.getElevationGain(), 0.01);
|
assertEquals(trackPoint.getElevationGain(), importedTrackPoint.getElevationGain(), 0.01);
|
||||||
}
|
}
|
||||||
|
if (verifyElevationLoss) {
|
||||||
|
assertEquals(trackPoint.getElevationLoss(), importedTrackPoint.getElevationLoss(), 0.01);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+93
@@ -0,0 +1,93 @@
|
|||||||
|
package de.dennisguse.opentracks.io.file.importer;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
import androidx.test.filters.LargeTest;
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.JUnit4;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
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.StringUtils;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Imports a legacy kml file with a statistics marker at the beginning
|
||||||
|
*/
|
||||||
|
@RunWith(JUnit4.class)
|
||||||
|
public class LegacyKmlImportTest {
|
||||||
|
|
||||||
|
private final Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
private final ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||||
|
|
||||||
|
private Track.Id importTrackId;
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() {
|
||||||
|
if (importTrackId != null) {
|
||||||
|
contentProviderUtils.deleteTrack(context, importTrackId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@LargeTest
|
||||||
|
@Test
|
||||||
|
public void kml_legacy_track_import() {
|
||||||
|
// given
|
||||||
|
KmlFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
|
||||||
|
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.legacy_track);
|
||||||
|
|
||||||
|
// when
|
||||||
|
// 1. import
|
||||||
|
importTrackId = trackImporter.importFile(inputStream);
|
||||||
|
|
||||||
|
// then
|
||||||
|
// 1. track
|
||||||
|
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||||
|
assertNotNull(importedTrack);
|
||||||
|
assertEquals("unknown", importedTrack.getCategory());
|
||||||
|
assertEquals("Test Track", importedTrack.getDescription());
|
||||||
|
assertEquals("2020-11-28 18:06", importedTrack.getName());
|
||||||
|
assertEquals("UNKNOWN", importedTrack.getIcon());
|
||||||
|
|
||||||
|
// 2. markers
|
||||||
|
assertEquals(0, contentProviderUtils.getMarkerCount(importTrackId));
|
||||||
|
|
||||||
|
// 3. trackpoints
|
||||||
|
List<TrackPoint> importedTrackPoints = contentProviderUtils.getTrackPoints(importTrackId);
|
||||||
|
assertEquals(8, importedTrackPoints.size()); // 6 trackpoints + pause and resume
|
||||||
|
|
||||||
|
// first 3 trackpoints
|
||||||
|
assertTrackpoint(importedTrackPoints.get(0), "2020-11-28T17:06:22.401Z", 1.234156, 12.340097, 469.286376953125);
|
||||||
|
assertTrackpoint(importedTrackPoints.get(1), "2020-11-28T17:06:25.448Z", 1.23415, 12.340036, 439.1626281738281);
|
||||||
|
assertTrackpoint(importedTrackPoints.get(2), "2020-11-28T17:06:47.888Z", 1.23405, 12.340057, 421.8070983886719);
|
||||||
|
|
||||||
|
// created pause trackpoint with time of previous valid trackpoint
|
||||||
|
assertTrackpoint(importedTrackPoints.get(3), "2020-11-28T17:06:47.888Z", 0.0, 100, 0.0);
|
||||||
|
// created resume trackpoint with time of next valid trackpoint
|
||||||
|
assertTrackpoint(importedTrackPoints.get(4), "2020-11-28T17:06:55.861Z", 0.0, 200, 0.0);
|
||||||
|
|
||||||
|
// last 3 trackpoints
|
||||||
|
assertTrackpoint(importedTrackPoints.get(5), "2020-11-28T17:06:55.861Z", 1.23405, 12.340057, 419.93902587890625);
|
||||||
|
assertTrackpoint(importedTrackPoints.get(6), "2020-11-28T17:06:56.905Z", 1.23405, 12.340057, 419.9036560058594);
|
||||||
|
assertTrackpoint(importedTrackPoints.get(7), "2020-11-28T17:07:20.870Z", 1.234046, 12.340082, 417.99432373046875);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertTrackpoint(final TrackPoint trackPoint, final String when, final double longitude, final double latitude, final double altitude) {
|
||||||
|
assertEquals(StringUtils.parseTime(when), trackPoint.getTime());
|
||||||
|
assertEquals(latitude, trackPoint.getLatitude(), 0.001d);
|
||||||
|
assertEquals(longitude, trackPoint.getLongitude(), 0.001d);
|
||||||
|
assertEquals(altitude, trackPoint.getAltitude(), 0.001d);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+16
-16
@@ -138,7 +138,7 @@ public class TrackRecordingServiceTest {
|
|||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testRecording_noTracks() throws Exception {
|
public void testRecording_noTracks() throws TimeoutException {
|
||||||
// given
|
// given
|
||||||
List<Track> tracks = contentProviderUtils.getTracks();
|
List<Track> tracks = contentProviderUtils.getTracks();
|
||||||
assertTrue(tracks.isEmpty());
|
assertTrue(tracks.isEmpty());
|
||||||
@@ -156,7 +156,7 @@ public class TrackRecordingServiceTest {
|
|||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testRecording_oldTracks() throws Exception {
|
public void testRecording_oldTracks() throws TimeoutException {
|
||||||
// given
|
// given
|
||||||
createDummyTrack(trackId, -1L, false);
|
createDummyTrack(trackId, -1L, false);
|
||||||
|
|
||||||
@@ -170,7 +170,7 @@ public class TrackRecordingServiceTest {
|
|||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testRecording_serviceRestart_whileRecording() throws Exception {
|
public void testRecording_serviceRestart_whileRecording() throws TimeoutException {
|
||||||
// given
|
// given
|
||||||
createDummyTrack(trackId, -1L, true);
|
createDummyTrack(trackId, -1L, true);
|
||||||
|
|
||||||
@@ -183,7 +183,7 @@ public class TrackRecordingServiceTest {
|
|||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testRecording_pauseAndResume() throws Exception {
|
public void testRecording_pauseAndResume() throws TimeoutException, InterruptedException {
|
||||||
// given
|
// given
|
||||||
createDummyTrack(trackId, -1L, true);
|
createDummyTrack(trackId, -1L, true);
|
||||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||||
@@ -212,7 +212,7 @@ public class TrackRecordingServiceTest {
|
|||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testRecording_resumeStoppedTrack() throws Exception {
|
public void testRecording_resumeStoppedTrack() throws TimeoutException, InterruptedException {
|
||||||
// given
|
// given
|
||||||
createDummyTrack(trackId, -1L, true);
|
createDummyTrack(trackId, -1L, true);
|
||||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||||
@@ -238,7 +238,7 @@ public class TrackRecordingServiceTest {
|
|||||||
@Ignore("Sometimes fails on CI.")
|
@Ignore("Sometimes fails on CI.")
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testRecording_orphanedRecordingTrack() throws Exception {
|
public void testRecording_orphanedRecordingTrack() throws TimeoutException {
|
||||||
// given
|
// given
|
||||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||||
|
|
||||||
@@ -254,7 +254,7 @@ public class TrackRecordingServiceTest {
|
|||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testStartNewTrack_alreadyRecording() throws Exception {
|
public void testStartNewTrack_alreadyRecording() throws TimeoutException {
|
||||||
// given
|
// given
|
||||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||||
service.startNewTrack();
|
service.startNewTrack();
|
||||||
@@ -274,7 +274,7 @@ public class TrackRecordingServiceTest {
|
|||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testEndCurrentTrack_noRecording() throws Exception {
|
public void testEndCurrentTrack_noRecording() throws TimeoutException {
|
||||||
// given
|
// given
|
||||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||||
assertFalse(service.isRecording());
|
assertFalse(service.isRecording());
|
||||||
@@ -290,21 +290,21 @@ public class TrackRecordingServiceTest {
|
|||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testInsertWaypointMarker_noRecordingTrack() throws Exception {
|
public void testInsertWaypointMarker_noRecordingTrack() throws TimeoutException {
|
||||||
// given
|
// given
|
||||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||||
assertFalse(service.isRecording());
|
assertFalse(service.isRecording());
|
||||||
|
|
||||||
// when
|
// when
|
||||||
Marker.Id waypointId = service.insertMarker(null, null, null, null);
|
Marker.Id markerId = service.insertMarker(null, null, null, null);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertNull(waypointId);
|
assertNull(markerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testInsertWaypointMarker_validWaypoint() throws Exception {
|
public void testInsertWaypointMarker_validWaypoint() throws TimeoutException, InterruptedException {
|
||||||
// given
|
// given
|
||||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||||
service.startNewTrack();
|
service.startNewTrack();
|
||||||
@@ -313,11 +313,11 @@ public class TrackRecordingServiceTest {
|
|||||||
Track.Id trackId = service.getRecordingTrackId();
|
Track.Id trackId = service.getRecordingTrackId();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
Marker.Id waypointId = service.insertMarker(null, null, null, null);
|
Marker.Id markerId = service.insertMarker(null, null, null, null);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertNotEquals(-1L, waypointId);
|
assertNotEquals(-1L, markerId);
|
||||||
Marker wpt = contentProviderUtils.getMarker(waypointId);
|
Marker wpt = contentProviderUtils.getMarker(markerId);
|
||||||
assertEquals(context.getString(R.string.marker_icon_url), wpt.getIcon());
|
assertEquals(context.getString(R.string.marker_icon_url), wpt.getIcon());
|
||||||
assertEquals(context.getString(R.string.marker_name_format, 1), wpt.getName());
|
assertEquals(context.getString(R.string.marker_name_format, 1), wpt.getName());
|
||||||
assertEquals(trackId, wpt.getTrackId());
|
assertEquals(trackId, wpt.getTrackId());
|
||||||
@@ -328,7 +328,7 @@ public class TrackRecordingServiceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void addTrack(Track track, boolean isRecording) {
|
private void addTrack(Track track, boolean isRecording) {
|
||||||
assertTrue(track.getId().isValid());
|
assertNotNull(track.getId());
|
||||||
contentProviderUtils.insertTrack(track);
|
contentProviderUtils.insertTrack(track);
|
||||||
assertEquals(track.getId(), contentProviderUtils.getTrack(track.getId()).getId());
|
assertEquals(track.getId(), contentProviderUtils.getTrack(track.getId()).getId());
|
||||||
PreferencesUtils.setLong(context, R.string.recording_track_id_key, isRecording ? track.getId().getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
|
PreferencesUtils.setLong(context, R.string.recording_track_id_key, isRecording ? track.getId().getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
|
||||||
|
|||||||
+1
-1
@@ -203,7 +203,7 @@ public class TrackRecordingServiceTestLooper {
|
|||||||
|
|
||||||
// Start a track.
|
// Start a track.
|
||||||
Track.Id trackId = service.startNewTrack();
|
Track.Id trackId = service.startNewTrack();
|
||||||
assertTrue(trackId.isValid());
|
assertNotNull(trackId);
|
||||||
assertTrue(service.isRecording());
|
assertTrue(service.isRecording());
|
||||||
Track track = contentProviderUtils.getTrack(trackId);
|
Track track = contentProviderUtils.getTrack(trackId);
|
||||||
assertNotNull(track);
|
assertNotNull(track);
|
||||||
|
|||||||
+2
-2
@@ -9,7 +9,7 @@ import org.junit.Test;
|
|||||||
*/
|
*/
|
||||||
public class ElevationSumManagerTest {
|
public class ElevationSumManagerTest {
|
||||||
|
|
||||||
private ElevationSumManager elevationSumManager = new ElevationSumManager();
|
private final ElevationSumManager elevationSumManager = new ElevationSumManager();
|
||||||
|
|
||||||
private static void addSensorValue(ElevationSumManager elevationSumManager, float[] values) {
|
private static void addSensorValue(ElevationSumManager elevationSumManager, float[] values) {
|
||||||
for (float f : values) {
|
for (float f : values) {
|
||||||
@@ -32,7 +32,7 @@ public class ElevationSumManagerTest {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
Assert.assertEquals(0f, elevationSumManager.getElevationGain_m(), 0.01);
|
Assert.assertEquals(0f, elevationSumManager.getElevationGain_m(), 0.01);
|
||||||
Assert.assertEquals(-48.0, elevationSumManager.getElevationLoss_m(), 0.01);
|
Assert.assertEquals(48.0, elevationSumManager.getElevationLoss_m(), 0.01);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
@@ -50,9 +49,8 @@ public class AnnouncementUtilsTest {
|
|||||||
stats.setMaxSpeed(100);
|
stats.setMaxSpeed(100);
|
||||||
stats.setTotalElevationGain(6000);
|
stats.setTotalElevationGain(6000);
|
||||||
|
|
||||||
IntervalStatistics intervalStatistics = new IntervalStatistics();
|
List<TrackPoint> trackPoints = TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 10).second;
|
||||||
List<TrackPoint> trackPoints = Arrays.asList(TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 10).second);
|
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, 1000);
|
||||||
intervalStatistics.build(trackPoints, 1000);
|
|
||||||
IntervalStatistics.Interval lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
|
IntervalStatistics.Interval lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
|
||||||
|
|
||||||
int speedId = R.plurals.voiceSpeedKilometersPerHour;
|
int speedId = R.plurals.voiceSpeedKilometersPerHour;
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public class PressureSensorUtilsTest {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(0f, elevationGain_m, 0.01);
|
assertEquals(0f, elevationGain_m, 0.01);
|
||||||
assertEquals(-15.0f, elevationLoss_m, 0.01);
|
assertEquals(15.0f, elevationLoss_m, 0.01);
|
||||||
assertEquals(1016.80f, lastUsedPressureValue_hPa, 0.01); //Expect exponential smoothing
|
assertEquals(1016.80f, lastUsedPressureValue_hPa, 0.01); //Expect exponential smoothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -226,4 +226,13 @@ public class StringUtilsTest {
|
|||||||
assertEquals("min/km", StringUtils.getSpeedParts(context, 0, true, false).second);
|
assertEquals("min/km", StringUtils.getSpeedParts(context, 0, true, false).second);
|
||||||
assertEquals("min/mi", StringUtils.getSpeedParts(context, 0, false, false).second);
|
assertEquals("min/mi", StringUtils.getSpeedParts(context, 0, false, false).second);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFormatSpeed() {
|
||||||
|
assertEquals("4:59 min/km", StringUtils.formatSpeed(context, 3.34, true, false));
|
||||||
|
assertEquals("8:02 min/mi", StringUtils.formatSpeed(context, 3.34, false, false));
|
||||||
|
|
||||||
|
assertEquals("12.02 km/h", StringUtils.formatSpeed(context, 3.34, true, true));
|
||||||
|
assertEquals("7.47 mph", StringUtils.formatSpeed(context, 3.34, false, true));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-27
@@ -8,6 +8,8 @@ import org.junit.Test;
|
|||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.junit.runners.JUnit4;
|
import org.junit.runners.JUnit4;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.content.data.Track;
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||||
@@ -57,8 +59,7 @@ public class AggregatedStatisticsTest {
|
|||||||
Track track = createTrack(context, totalDistance, totalTime, biking);
|
Track track = createTrack(context, totalDistance, totalTime, biking);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics();
|
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics(List.of(track));
|
||||||
aggregatedStatistics.aggregate(track);
|
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(1, aggregatedStatistics.getCount());
|
assertEquals(1, aggregatedStatistics.getCount());
|
||||||
@@ -80,8 +81,7 @@ public class AggregatedStatisticsTest {
|
|||||||
Track track = createTrack(context, totalDistance, totalTime, mountainBiking);
|
Track track = createTrack(context, totalDistance, totalTime, mountainBiking);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics();
|
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics(List.of(track));
|
||||||
aggregatedStatistics.aggregate(track);
|
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertNotNull(aggregatedStatistics.get(mountainBiking));
|
assertNotNull(aggregatedStatistics.get(mountainBiking));
|
||||||
@@ -97,8 +97,7 @@ public class AggregatedStatisticsTest {
|
|||||||
Track track = createTrack(context, totalDistance, totalTime, trailRunning);
|
Track track = createTrack(context, totalDistance, totalTime, trailRunning);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics();
|
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics(List.of(track));
|
||||||
aggregatedStatistics.aggregate(track);
|
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertNotNull(aggregatedStatistics.get(trailRunning));
|
assertNotNull(aggregatedStatistics.get(trailRunning));
|
||||||
@@ -111,15 +110,10 @@ public class AggregatedStatisticsTest {
|
|||||||
long totalDistance = 10000;
|
long totalDistance = 10000;
|
||||||
long totalTime = 2400000;
|
long totalTime = 2400000;
|
||||||
String biking = context.getString(R.string.activity_type_biking);
|
String biking = context.getString(R.string.activity_type_biking);
|
||||||
Track[] tracks = new Track[2];
|
List<Track> tracks = List.of(createTrack(context, totalDistance, totalTime, biking), createTrack(context, totalDistance, totalTime, biking));
|
||||||
for (int i = 0; i < 2; i++) {
|
|
||||||
tracks[i] = createTrack(context, totalDistance, totalTime, biking);
|
|
||||||
}
|
|
||||||
|
|
||||||
// when
|
// when
|
||||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics();
|
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics(tracks);
|
||||||
aggregatedStatistics.aggregate(tracks[0]); // biking activity 1.
|
|
||||||
aggregatedStatistics.aggregate(tracks[1]); // biking activity 2.
|
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(1, aggregatedStatistics.getCount());
|
assertEquals(1, aggregatedStatistics.getCount());
|
||||||
@@ -141,17 +135,14 @@ public class AggregatedStatisticsTest {
|
|||||||
String running = context.getString(R.string.activity_type_running);
|
String running = context.getString(R.string.activity_type_running);
|
||||||
String walking = context.getString(R.string.activity_type_walking);
|
String walking = context.getString(R.string.activity_type_walking);
|
||||||
long totalTime = 2400000;
|
long totalTime = 2400000;
|
||||||
Track[] tracks = new Track[]{
|
List<Track> tracks = List.of(
|
||||||
createTrack(context, totalDistance, totalTime, biking),
|
createTrack(context, totalDistance, totalTime, biking),
|
||||||
createTrack(context, totalDistance, totalTime, running),
|
createTrack(context, totalDistance, totalTime, running),
|
||||||
createTrack(context, totalDistance, totalTime, walking)
|
createTrack(context, totalDistance, totalTime, walking)
|
||||||
};
|
);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics();
|
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics(tracks);
|
||||||
aggregatedStatistics.aggregate(tracks[0]); // biking activity 1.
|
|
||||||
aggregatedStatistics.aggregate(tracks[1]); // biking activity 2.
|
|
||||||
aggregatedStatistics.aggregate(tracks[2]); // biking activity 3.
|
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(3, aggregatedStatistics.getCount());
|
assertEquals(3, aggregatedStatistics.getCount());
|
||||||
@@ -191,7 +182,7 @@ public class AggregatedStatisticsTest {
|
|||||||
String running = context.getString(R.string.activity_type_running);
|
String running = context.getString(R.string.activity_type_running);
|
||||||
String walking = context.getString(R.string.activity_type_walking);
|
String walking = context.getString(R.string.activity_type_walking);
|
||||||
String driving = context.getString(R.string.activity_type_driving);
|
String driving = context.getString(R.string.activity_type_driving);
|
||||||
Track[] tracks = new Track[]{
|
List<Track> tracks = List.of(
|
||||||
createTrack(context, totalDistance, totalTime, biking),
|
createTrack(context, totalDistance, totalTime, biking),
|
||||||
createTrack(context, totalDistance, totalTime, running),
|
createTrack(context, totalDistance, totalTime, running),
|
||||||
createTrack(context, totalDistance, totalTime, walking),
|
createTrack(context, totalDistance, totalTime, walking),
|
||||||
@@ -201,16 +192,12 @@ public class AggregatedStatisticsTest {
|
|||||||
createTrack(context, totalDistance, totalTime, biking),
|
createTrack(context, totalDistance, totalTime, biking),
|
||||||
createTrack(context, totalDistance, totalTime, biking),
|
createTrack(context, totalDistance, totalTime, biking),
|
||||||
createTrack(context, totalDistance, totalTime, biking),
|
createTrack(context, totalDistance, totalTime, biking),
|
||||||
createTrack(context, totalDistance, totalTime, driving),
|
createTrack(context, totalDistance, totalTime, driving)
|
||||||
};
|
);
|
||||||
|
|
||||||
|
|
||||||
// when
|
// when
|
||||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics();
|
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics(tracks);
|
||||||
for (Track track : tracks) {
|
|
||||||
aggregatedStatistics.aggregate(track);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// then
|
// then
|
||||||
// 4 sports.
|
// 4 sports.
|
||||||
@@ -255,5 +242,12 @@ public class AggregatedStatisticsTest {
|
|||||||
assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
|
assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
|
||||||
assertEquals(totalTime, statistics2.getMovingTime());
|
assertEquals(totalTime, statistics2.getMovingTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check order
|
||||||
|
|
||||||
|
{
|
||||||
|
assertEquals(biking, aggregatedStatistics.getItem(0).getCategory());
|
||||||
|
assertEquals(driving, aggregatedStatistics.getItem(3).getCategory());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-17
@@ -1,12 +1,9 @@
|
|||||||
package de.dennisguse.opentracks.viewmodels;
|
package de.dennisguse.opentracks.viewmodels;
|
||||||
|
|
||||||
import android.util.Pair;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.junit.runners.JUnit4;
|
import org.junit.runners.JUnit4;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||||
@@ -24,8 +21,7 @@ public class IntervalStatisticsTest {
|
|||||||
private static final String TAG = IntervalStatisticsTest.class.getSimpleName();
|
private static final String TAG = IntervalStatisticsTest.class.getSimpleName();
|
||||||
|
|
||||||
private List<TrackPoint> buildTrackPoints(int numberOfTrackPoints) {
|
private List<TrackPoint> buildTrackPoints(int numberOfTrackPoints) {
|
||||||
Pair<Track, TrackPoint[]> pair = TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), numberOfTrackPoints);
|
return TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), numberOfTrackPoints).second;
|
||||||
return Arrays.asList(pair.second);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private TrackStatistics buildTrackStatistics(List<TrackPoint> trackPoints) {
|
private TrackStatistics buildTrackStatistics(List<TrackPoint> trackPoints) {
|
||||||
@@ -47,10 +43,9 @@ public class IntervalStatisticsTest {
|
|||||||
List<TrackPoint> trackPoints = buildTrackPoints(50);
|
List<TrackPoint> trackPoints = buildTrackPoints(50);
|
||||||
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
|
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
|
||||||
float distanceInterval = 1000f;
|
float distanceInterval = 1000f;
|
||||||
IntervalStatistics intervalStatistics = new IntervalStatistics();
|
|
||||||
|
|
||||||
// when and then
|
// when and then
|
||||||
whenAndThen(trackPoints, trackStatistics, distanceInterval, intervalStatistics);
|
whenAndThen(trackPoints, trackStatistics, distanceInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -64,10 +59,9 @@ public class IntervalStatisticsTest {
|
|||||||
List<TrackPoint> trackPoints = buildTrackPoints(200);
|
List<TrackPoint> trackPoints = buildTrackPoints(200);
|
||||||
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
|
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
|
||||||
float distanceInterval = 1000f;
|
float distanceInterval = 1000f;
|
||||||
IntervalStatistics intervalStatistics = new IntervalStatistics();
|
|
||||||
|
|
||||||
// when and then
|
// when and then
|
||||||
whenAndThen(trackPoints, trackStatistics, distanceInterval, intervalStatistics);
|
whenAndThen(trackPoints, trackStatistics, distanceInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -81,10 +75,9 @@ public class IntervalStatisticsTest {
|
|||||||
List<TrackPoint> trackPoints = buildTrackPoints(200);
|
List<TrackPoint> trackPoints = buildTrackPoints(200);
|
||||||
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
|
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
|
||||||
float distanceInterval = 3000f;
|
float distanceInterval = 3000f;
|
||||||
IntervalStatistics intervalStatistics = new IntervalStatistics();
|
|
||||||
|
|
||||||
// when and then
|
// when and then
|
||||||
whenAndThen(trackPoints, trackStatistics, distanceInterval, intervalStatistics);
|
whenAndThen(trackPoints, trackStatistics, distanceInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -98,10 +91,9 @@ public class IntervalStatisticsTest {
|
|||||||
List<TrackPoint> trackPoints = buildTrackPoints(1000);
|
List<TrackPoint> trackPoints = buildTrackPoints(1000);
|
||||||
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
|
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
|
||||||
float distanceInterval = 3000f;
|
float distanceInterval = 3000f;
|
||||||
IntervalStatistics intervalStatistics = new IntervalStatistics();
|
|
||||||
|
|
||||||
// when and then
|
// when and then
|
||||||
whenAndThen(trackPoints, trackStatistics, distanceInterval, intervalStatistics);
|
whenAndThen(trackPoints, trackStatistics, distanceInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -115,14 +107,13 @@ public class IntervalStatisticsTest {
|
|||||||
List<TrackPoint> trackPoints = buildTrackPoints(10000);
|
List<TrackPoint> trackPoints = buildTrackPoints(10000);
|
||||||
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
|
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
|
||||||
float distanceInterval = 1000f;
|
float distanceInterval = 1000f;
|
||||||
IntervalStatistics intervalStatistics = new IntervalStatistics();
|
|
||||||
|
|
||||||
// when and then
|
// when and then
|
||||||
whenAndThen(trackPoints, trackStatistics, distanceInterval, intervalStatistics);
|
whenAndThen(trackPoints, trackStatistics, distanceInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void whenAndThen(List<TrackPoint> trackPoints, TrackStatistics trackStatistics, float distanceInterval, IntervalStatistics intervalStatistics) {
|
private void whenAndThen(List<TrackPoint> trackPoints, TrackStatistics trackStatistics, float distanceInterval) {
|
||||||
intervalStatistics.build(trackPoints, distanceInterval);
|
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, distanceInterval);
|
||||||
List<IntervalStatistics.Interval> intervalList = intervalStatistics.getIntervalList();
|
List<IntervalStatistics.Interval> intervalList = intervalStatistics.getIntervalList();
|
||||||
double totalDistance = 0d;
|
double totalDistance = 0d;
|
||||||
long totalTime = 0L;
|
long totalTime = 0L;
|
||||||
|
|||||||
@@ -0,0 +1,173 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<kml xmlns="http://www.opengis.net/kml/2.2"
|
||||||
|
xmlns:gx="http://www.google.com/kml/ext/2.2"
|
||||||
|
xmlns:atom="http://www.w3.org/2005/Atom"
|
||||||
|
xmlns:opentracks="http://opentracksapp.com/xmlschemas/v1">
|
||||||
|
<Document>
|
||||||
|
<open>1</open>
|
||||||
|
<visibility>1</visibility>
|
||||||
|
<name><![CDATA[2020-11-28 18:06]]></name>
|
||||||
|
<atom:generator><![CDATA[OpenTracks (Debug)]]></atom:generator>
|
||||||
|
<Style id="track">
|
||||||
|
<LineStyle><color>7f0000ff</color><width>4</width></LineStyle>
|
||||||
|
<IconStyle>
|
||||||
|
<scale>1.3</scale>
|
||||||
|
<Icon><href>http://earth.google.com/images/kml-icons/track-directional/track-0.png</href></Icon>
|
||||||
|
</IconStyle>
|
||||||
|
</Style>
|
||||||
|
<Style id="start"><IconStyle>
|
||||||
|
<scale>1.3</scale>
|
||||||
|
<Icon><href>http://maps.google.com/mapfiles/kml/paddle/grn-circle.png</href></Icon>
|
||||||
|
<hotSpot x="32" y="1" xunits="pixels" yunits="pixels"/>
|
||||||
|
</IconStyle></Style>
|
||||||
|
<Style id="end"><IconStyle>
|
||||||
|
<scale>1.3</scale>
|
||||||
|
<Icon><href>http://maps.google.com/mapfiles/kml/paddle/red-circle.png</href></Icon>
|
||||||
|
<hotSpot x="32" y="1" xunits="pixels" yunits="pixels"/>
|
||||||
|
</IconStyle></Style>
|
||||||
|
<Style id="waypoint"><IconStyle>
|
||||||
|
<scale>1.3</scale>
|
||||||
|
<Icon><href>http://maps.google.com/mapfiles/kml/pushpin/blue-pushpin.png</href></Icon>
|
||||||
|
<hotSpot x="20" y="2" xunits="pixels" yunits="pixels"/>
|
||||||
|
</IconStyle></Style>
|
||||||
|
<Schema id="schema">
|
||||||
|
<gx:SimpleArrayField name="speed" type="float">
|
||||||
|
<displayName><![CDATA[Speed (m/s)]]></displayName>
|
||||||
|
</gx:SimpleArrayField>
|
||||||
|
<gx:SimpleArrayField name="power" type="float">
|
||||||
|
<displayName><![CDATA[Power (W)]]></displayName>
|
||||||
|
</gx:SimpleArrayField>
|
||||||
|
<gx:SimpleArrayField name="cadence" type="float">
|
||||||
|
<displayName><![CDATA[Cadence (rpm)]]></displayName>
|
||||||
|
</gx:SimpleArrayField>
|
||||||
|
<gx:SimpleArrayField name="heart_rate" type="float">
|
||||||
|
<displayName><![CDATA[Heart rate (bpm)]]></displayName>
|
||||||
|
</gx:SimpleArrayField>
|
||||||
|
</Schema>
|
||||||
|
<Folder>
|
||||||
|
<name><![CDATA[Sun, 28 Nov 2020 18:06 Markers]]></name>
|
||||||
|
<open>1</open>
|
||||||
|
<Placemark>
|
||||||
|
<name><![CDATA[Split 0]]></name>
|
||||||
|
<description><![CDATA[Total distance: 0.00 km (0.0 mi)
|
||||||
|
Total time: 00:00
|
||||||
|
Moving time: 00:00
|
||||||
|
Average speed: 0.00 km/h (0.0 mi/h)
|
||||||
|
Average moving speed: 0.00 km/h (0.0 mi/h)
|
||||||
|
Max speed: 0.00 km/h (0.0 mi/h)
|
||||||
|
Average pace: 0:00 min/km (0:00 min/mi)
|
||||||
|
Average moving pace: 0:00 min/km (0:00 min/mi)
|
||||||
|
Fastest pace: 0:00 min/km (0:00 min/mi)
|
||||||
|
Max elevation: -9223372036854775808 m (-9223372036854775808 ft)
|
||||||
|
Min elevation: 9223372036854775807 m (9223372036854775807 ft)
|
||||||
|
Elevation gain: 0 m (0 ft)
|
||||||
|
Max grade: 0 %
|
||||||
|
Min grade: 0 %
|
||||||
|
Recorded: 6/23 11:29
|
||||||
|
]]></description>
|
||||||
|
<TimeStamp><when>1970-01-01T00:00:00Z</when></TimeStamp>
|
||||||
|
<styleUrl>#waypoint</styleUrl>
|
||||||
|
<Point>
|
||||||
|
<coordinates>180.0,100.0</coordinates>
|
||||||
|
</Point>
|
||||||
|
</Placemark>
|
||||||
|
</Folder>
|
||||||
|
<Placemark>
|
||||||
|
<name><![CDATA[2020-11-28 18:06 (Start)]]></name>
|
||||||
|
<description><![CDATA[]]></description>
|
||||||
|
<TimeStamp><when>2020-11-28T17:06:22.401Z</when></TimeStamp>
|
||||||
|
<styleUrl>#start</styleUrl>
|
||||||
|
<Point>
|
||||||
|
<coordinates>1.234156,12.340097,469.286376953125</coordinates>
|
||||||
|
</Point>
|
||||||
|
</Placemark>
|
||||||
|
<Placemark>
|
||||||
|
<name><![CDATA[2020-11-28 18:06]]></name>
|
||||||
|
<description><![CDATA[Test Track]]></description>
|
||||||
|
<icon><![CDATA[UNKNOWN]]></icon>
|
||||||
|
<opentracks:trackid>2f170875-55d7-445d-b2b8-22a573c81dbc</opentracks:trackid>
|
||||||
|
<styleUrl>#track</styleUrl>
|
||||||
|
<ExtendedData>
|
||||||
|
<Data name="type"><value><![CDATA[unknown]]></value></Data>
|
||||||
|
</ExtendedData>
|
||||||
|
<gx:MultiTrack>
|
||||||
|
<altitudeMode>absolute</altitudeMode>
|
||||||
|
<gx:interpolate>1</gx:interpolate>
|
||||||
|
<gx:Track>
|
||||||
|
<when>2020-11-28T17:06:22.401Z</when>
|
||||||
|
<gx:coord>1.234156 12.340097 469.286376953125</gx:coord>
|
||||||
|
<when>2020-11-28T17:06:25.448Z</when>
|
||||||
|
<gx:coord>1.23415 12.340036 439.1626281738281</gx:coord>
|
||||||
|
<when>2020-11-28T17:06:47.888Z</when>
|
||||||
|
<gx:coord>1.23405 12.340057 421.8070983886719</gx:coord>
|
||||||
|
<ExtendedData>
|
||||||
|
<SchemaData schemaUrl="#schema">
|
||||||
|
<gx:SimpleArrayData name="speed">
|
||||||
|
<gx:value>0.5391469</gx:value>
|
||||||
|
<gx:value>0.15777911</gx:value>
|
||||||
|
<gx:value>0.0</gx:value>
|
||||||
|
</gx:SimpleArrayData>
|
||||||
|
<gx:SimpleArrayData name="elevation_gain">
|
||||||
|
<gx:value>0.0</gx:value>
|
||||||
|
<gx:value>0.0</gx:value>
|
||||||
|
<gx:value>0.0</gx:value>
|
||||||
|
</gx:SimpleArrayData>
|
||||||
|
</SchemaData>
|
||||||
|
</ExtendedData>
|
||||||
|
</gx:Track>
|
||||||
|
<gx:Track>
|
||||||
|
<when>2020-11-28T17:06:55.861Z</when>
|
||||||
|
<gx:coord>1.23405 12.340057 419.93902587890625</gx:coord>
|
||||||
|
<when>2020-11-28T17:06:56.905Z</when>
|
||||||
|
<gx:coord>1.23405 12.340057 419.9036560058594</gx:coord>
|
||||||
|
<when>2020-11-28T17:07:20.870Z</when>
|
||||||
|
<gx:coord>1.234046 12.340082 417.99432373046875</gx:coord>
|
||||||
|
<ExtendedData>
|
||||||
|
<SchemaData schemaUrl="#schema">
|
||||||
|
<gx:SimpleArrayData name="speed">
|
||||||
|
<gx:value>0.0</gx:value>
|
||||||
|
<gx:value>0.0</gx:value>
|
||||||
|
<gx:value>0.0</gx:value>
|
||||||
|
</gx:SimpleArrayData>
|
||||||
|
<gx:SimpleArrayData name="elevation_gain">
|
||||||
|
<gx:value>0.0</gx:value>
|
||||||
|
<gx:value>0.0</gx:value>
|
||||||
|
<gx:value>0.0</gx:value>
|
||||||
|
<gx:value>0.0</gx:value>
|
||||||
|
<gx:value>0.0</gx:value>
|
||||||
|
<gx:value>0.0</gx:value>
|
||||||
|
</gx:SimpleArrayData>
|
||||||
|
</SchemaData>
|
||||||
|
</ExtendedData>
|
||||||
|
</gx:Track>
|
||||||
|
</gx:MultiTrack>
|
||||||
|
</Placemark>
|
||||||
|
<Placemark>
|
||||||
|
<name><![CDATA[2020-11-28 18:06 (End)]]></name>
|
||||||
|
<description><![CDATA[OpenTracks (Debug)
|
||||||
|
|
||||||
|
Name: 2020-11-28 18:06
|
||||||
|
Activity type: unknown
|
||||||
|
Description: -
|
||||||
|
Total distance: 0.01 km (0.0 mi)
|
||||||
|
Total time: 01:04
|
||||||
|
Moving time: 00:00
|
||||||
|
Average speed: 0.49 km/h (0.3 mi/h)
|
||||||
|
Average moving speed: 0.00 km/h (0.0 mi/h)
|
||||||
|
Max speed: 0.00 km/h (0.0 mi/h)
|
||||||
|
Average pace: 122:30 min/km (197:08 min/mi)
|
||||||
|
Average moving pace: 0:00 min/km (0:00 min/mi)
|
||||||
|
Fastest pace: 0:00 min/km (0:00 min/mi)
|
||||||
|
Max elevation: 469 m (1540 ft)
|
||||||
|
Min elevation: 419 m (1376 ft)
|
||||||
|
Elevation gain: 0 m (0 ft)
|
||||||
|
Recorded: 11/28 18:06
|
||||||
|
]]></description>
|
||||||
|
<TimeStamp><when>2020-11-28T17:07:20.870Z</when></TimeStamp>
|
||||||
|
<styleUrl>#end</styleUrl>
|
||||||
|
<Point>
|
||||||
|
<coordinates>1.234046,12.340082,417.99432373046875</coordinates>
|
||||||
|
</Point>
|
||||||
|
</Placemark>
|
||||||
|
</Document>
|
||||||
|
</kml>
|
||||||
@@ -50,10 +50,11 @@ limitations under the License.
|
|||||||
<application
|
<application
|
||||||
android:name=".Startup"
|
android:name=".Startup"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
android:hasFragileUserData="true"
|
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
|
android:hasFragileUserData="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
android:supportsRtl="true"
|
||||||
android:theme="@style/ThemeCustom">
|
android:theme="@style/ThemeCustom">
|
||||||
|
|
||||||
<activity android:name=".AboutActivity" />
|
<activity android:name=".AboutActivity" />
|
||||||
@@ -71,14 +72,14 @@ limitations under the License.
|
|||||||
<activity android:name=".io.file.importer.ImportActivity">
|
<activity android:name=".io.file.importer.ImportActivity">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEND"/>
|
<action android:name="android.intent.action.SEND" />
|
||||||
<category android:name="android.intent.category.DEFAULT"/>
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<data android:mimeType="application/gpx+xml"/>
|
<data android:mimeType="application/gpx+xml" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEND"/>
|
<action android:name="android.intent.action.SEND" />
|
||||||
<category android:name="android.intent.category.DEFAULT"/>
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<data android:mimeType="application/gpx"/>
|
<data android:mimeType="application/gpx" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<intent-filter
|
<intent-filter
|
||||||
@@ -86,13 +87,15 @@ limitations under the License.
|
|||||||
android:priority="50">
|
android:priority="50">
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<action android:name="android.intent.action.SEND" />
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<data android:scheme="file"/>
|
|
||||||
<data android:scheme="content"/>
|
<data android:scheme="file" />
|
||||||
<data android:scheme="data"/>
|
<data android:scheme="content" />
|
||||||
<data android:host="*"/>
|
<data android:scheme="data" />
|
||||||
<data android:mimeType="*/*"/>
|
<data android:host="*" />
|
||||||
|
<data android:mimeType="*/*" />
|
||||||
<data android:pathPattern=".*\\.gpx" />
|
<data android:pathPattern=".*\\.gpx" />
|
||||||
<data android:pathPattern=".*\\..*\\.gpx" />
|
<data android:pathPattern=".*\\..*\\.gpx" />
|
||||||
<data android:pathPattern=".*\\..*\\..*\\.gpx" />
|
<data android:pathPattern=".*\\..*\\..*\\.gpx" />
|
||||||
@@ -110,12 +113,14 @@ limitations under the License.
|
|||||||
android:priority="50">
|
android:priority="50">
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<action android:name="android.intent.action.SEND" />
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<data android:scheme="file"/>
|
|
||||||
<data android:scheme="content"/>
|
<data android:scheme="file" />
|
||||||
<data android:scheme="data"/>
|
<data android:scheme="content" />
|
||||||
<data android:host="*"/>
|
<data android:scheme="data" />
|
||||||
|
<data android:host="*" />
|
||||||
<data android:pathPattern=".*\\.gpx" />
|
<data android:pathPattern=".*\\.gpx" />
|
||||||
<data android:pathPattern=".*\\..*\\.gpx" />
|
<data android:pathPattern=".*\\..*\\.gpx" />
|
||||||
<data android:pathPattern=".*\\..*\\..*\\.gpx" />
|
<data android:pathPattern=".*\\..*\\..*\\.gpx" />
|
||||||
@@ -129,9 +134,9 @@ limitations under the License.
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEND"/>
|
<action android:name="android.intent.action.SEND" />
|
||||||
<category android:name="android.intent.category.DEFAULT"/>
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<data android:mimeType="application/vnd.google-earth.kmz"/>
|
<data android:mimeType="application/vnd.google-earth.kmz" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<intent-filter
|
<intent-filter
|
||||||
@@ -139,13 +144,15 @@ limitations under the License.
|
|||||||
android:priority="50">
|
android:priority="50">
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<action android:name="android.intent.action.SEND" />
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<data android:scheme="file"/>
|
|
||||||
<data android:scheme="content"/>
|
<data android:scheme="file" />
|
||||||
<data android:scheme="data"/>
|
<data android:scheme="content" />
|
||||||
<data android:host="*"/>
|
<data android:scheme="data" />
|
||||||
<data android:mimeType="*/*"/>
|
<data android:host="*" />
|
||||||
|
<data android:mimeType="*/*" />
|
||||||
<data android:pathPattern=".*\\.kmz" />
|
<data android:pathPattern=".*\\.kmz" />
|
||||||
<data android:pathPattern=".*\\..*\\.kmz" />
|
<data android:pathPattern=".*\\..*\\.kmz" />
|
||||||
<data android:pathPattern=".*\\..*\\..*\\.kmz" />
|
<data android:pathPattern=".*\\..*\\..*\\.kmz" />
|
||||||
@@ -163,12 +170,14 @@ limitations under the License.
|
|||||||
android:priority="50">
|
android:priority="50">
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<action android:name="android.intent.action.SEND" />
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<data android:scheme="file"/>
|
|
||||||
<data android:scheme="content"/>
|
<data android:scheme="file" />
|
||||||
<data android:scheme="data"/>
|
<data android:scheme="content" />
|
||||||
<data android:host="*"/>
|
<data android:scheme="data" />
|
||||||
|
<data android:host="*" />
|
||||||
<data android:pathPattern=".*\\.kmz" />
|
<data android:pathPattern=".*\\.kmz" />
|
||||||
<data android:pathPattern=".*\\..*\\.kmz" />
|
<data android:pathPattern=".*\\..*\\.kmz" />
|
||||||
<data android:pathPattern=".*\\..*\\..*\\.kmz" />
|
<data android:pathPattern=".*\\..*\\..*\\.kmz" />
|
||||||
@@ -182,9 +191,9 @@ limitations under the License.
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEND"/>
|
<action android:name="android.intent.action.SEND" />
|
||||||
<category android:name="android.intent.category.DEFAULT"/>
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<data android:mimeType="application/vnd.google-earth.kml+xml"/>
|
<data android:mimeType="application/vnd.google-earth.kml+xml" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<intent-filter
|
<intent-filter
|
||||||
@@ -192,13 +201,15 @@ limitations under the License.
|
|||||||
android:priority="50">
|
android:priority="50">
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<action android:name="android.intent.action.SEND" />
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<data android:scheme="file"/>
|
|
||||||
<data android:scheme="content"/>
|
<data android:scheme="file" />
|
||||||
<data android:scheme="data"/>
|
<data android:scheme="content" />
|
||||||
<data android:host="*"/>
|
<data android:scheme="data" />
|
||||||
<data android:mimeType="*/*"/>
|
<data android:host="*" />
|
||||||
|
<data android:mimeType="*/*" />
|
||||||
<data android:pathPattern=".*\\.kml" />
|
<data android:pathPattern=".*\\.kml" />
|
||||||
<data android:pathPattern=".*\\..*\\.kml" />
|
<data android:pathPattern=".*\\..*\\.kml" />
|
||||||
<data android:pathPattern=".*\\..*\\..*\\.kml" />
|
<data android:pathPattern=".*\\..*\\..*\\.kml" />
|
||||||
@@ -216,12 +227,14 @@ limitations under the License.
|
|||||||
android:priority="50">
|
android:priority="50">
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<action android:name="android.intent.action.SEND" />
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<data android:scheme="file"/>
|
|
||||||
<data android:scheme="content"/>
|
<data android:scheme="file" />
|
||||||
<data android:scheme="data"/>
|
<data android:scheme="content" />
|
||||||
<data android:host="*"/>
|
<data android:scheme="data" />
|
||||||
|
<data android:host="*" />
|
||||||
<data android:pathPattern=".*\\.kml" />
|
<data android:pathPattern=".*\\.kml" />
|
||||||
<data android:pathPattern=".*\\..*\\.kml" />
|
<data android:pathPattern=".*\\..*\\.kml" />
|
||||||
<data android:pathPattern=".*\\..*\\..*\\.kml" />
|
<data android:pathPattern=".*\\..*\\..*\\.kml" />
|
||||||
@@ -237,7 +250,7 @@ limitations under the License.
|
|||||||
|
|
||||||
<activity android:name=".MarkerDetailActivity">
|
<activity android:name=".MarkerDetailActivity">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="de.dennisguse.opentracks.MarkerDetails"/>
|
<action android:name="de.dennisguse.opentracks.MarkerDetails" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
@@ -258,8 +271,8 @@ limitations under the License.
|
|||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".TrackListActivity"
|
android:name=".TrackListActivity"
|
||||||
android:theme="@style/SplashTheme"
|
android:launchMode="singleTop"
|
||||||
android:launchMode="singleTop">
|
android:theme="@style/SplashTheme">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package de.dennisguse.opentracks;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
|
||||||
@@ -39,8 +40,22 @@ public abstract class AbstractActivity extends AppCompatActivity {
|
|||||||
setContentView(getRootView());
|
setContentView(getRootView());
|
||||||
|
|
||||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
setSupportActionBar(toolbar);
|
setupActionBarBack(toolbar);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onSupportNavigateUp() {
|
||||||
|
onBackPressed();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract View getRootView();
|
protected abstract View getRootView();
|
||||||
|
|
||||||
|
protected void setupActionBarBack(@Nullable Toolbar toolbar) {
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
if (toolbar != null) {
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
getSupportActionBar().setDisplayShowHomeEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,15 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
|
|||||||
protected void onCreate(Bundle bundle) {
|
protected void onCreate(Bundle bundle) {
|
||||||
super.onCreate(bundle);
|
super.onCreate(bundle);
|
||||||
|
|
||||||
Marker.Id markerId = getIntent().getParcelableExtra(EXTRA_MARKER_ID);
|
Object bundleMarkerId = getIntent().getExtras().get(EXTRA_MARKER_ID);
|
||||||
|
Marker.Id markerId = null;
|
||||||
|
if (bundleMarkerId instanceof Marker.Id) {
|
||||||
|
markerId = (Marker.Id) bundleMarkerId;
|
||||||
|
}
|
||||||
|
if (bundleMarkerId instanceof Long) {
|
||||||
|
//Incoming Intent via Dashboard API.
|
||||||
|
markerId = new Marker.Id((Long) bundleMarkerId);
|
||||||
|
}
|
||||||
if (markerId == null) {
|
if (markerId == null) {
|
||||||
Log.d(TAG, "invalid marker id");
|
Log.d(TAG, "invalid marker id");
|
||||||
finish();
|
finish();
|
||||||
|
|||||||
@@ -33,20 +33,16 @@ import android.widget.ArrayAdapter;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.data.Marker;
|
import de.dennisguse.opentracks.content.data.Marker;
|
||||||
import de.dennisguse.opentracks.content.data.Track;
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
|
||||||
import de.dennisguse.opentracks.databinding.MarkerEditBinding;
|
import de.dennisguse.opentracks.databinding.MarkerEditBinding;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
|
||||||
import de.dennisguse.opentracks.util.FileUtils;
|
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
|
import de.dennisguse.opentracks.viewmodels.MarkerEditViewModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An activity to add/edit a marker.
|
* An activity to add/edit a marker.
|
||||||
@@ -58,25 +54,20 @@ public class MarkerEditActivity extends AbstractActivity {
|
|||||||
public static final String EXTRA_TRACK_ID = "track_id";
|
public static final String EXTRA_TRACK_ID = "track_id";
|
||||||
public static final String EXTRA_MARKER_ID = "marker_id";
|
public static final String EXTRA_MARKER_ID = "marker_id";
|
||||||
|
|
||||||
private static final String BUNDLE_PHOTO_URI = "photo_uri";
|
|
||||||
|
|
||||||
private static final int CAMERA_REQUEST_CODE = 5;
|
private static final int CAMERA_REQUEST_CODE = 5;
|
||||||
private static final int GALLERY_IMG_REQUEST_CODE = 7;
|
private static final int GALLERY_IMG_REQUEST_CODE = 7;
|
||||||
|
|
||||||
private static final String TAG = MarkerEditActivity.class.getSimpleName();
|
private static final String TAG = MarkerEditActivity.class.getSimpleName();
|
||||||
private Track.Id trackId;
|
private Track.Id trackId;
|
||||||
private final TrackRecordingServiceConnection trackRecordingServiceConnection = new TrackRecordingServiceConnection();
|
|
||||||
private Marker marker;
|
private Marker marker;
|
||||||
|
|
||||||
private MenuItem insertPhotoMenuItem;
|
private MenuItem insertPhotoMenuItem;
|
||||||
private MenuItem insertGalleryImgMenuItem;
|
private MenuItem insertGalleryImgMenuItem;
|
||||||
|
|
||||||
private Uri photoUri;
|
|
||||||
private Uri photoUriOriginal;
|
|
||||||
private List<Uri> photoUriDeleteList = new ArrayList<>();
|
|
||||||
private boolean hasCamera;
|
private boolean hasCamera;
|
||||||
|
private Uri cameraPhotoUri;
|
||||||
|
|
||||||
private boolean isNewMarker;
|
private MarkerEditViewModel viewModel;
|
||||||
|
|
||||||
// UI elements
|
// UI elements
|
||||||
private MarkerEditBinding viewBinding;
|
private MarkerEditBinding viewBinding;
|
||||||
@@ -94,117 +85,40 @@ public class MarkerEditActivity extends AbstractActivity {
|
|||||||
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.marker_types, android.R.layout.simple_dropdown_item_1line);
|
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.marker_types, android.R.layout.simple_dropdown_item_1line);
|
||||||
viewBinding.markerEditMarkerType.setAdapter(adapter);
|
viewBinding.markerEditMarkerType.setAdapter(adapter);
|
||||||
viewBinding.markerEditPhotoDelete.setOnClickListener(v -> {
|
viewBinding.markerEditPhotoDelete.setOnClickListener(v -> {
|
||||||
if (marker != null && marker.hasPhoto()) {
|
viewModel.onPhotoDelete(viewBinding.markerEditName.getText().toString(),
|
||||||
marker.setPhotoUrl(null);
|
viewBinding.markerEditMarkerType.getText().toString(),
|
||||||
}
|
viewBinding.markerEditDescription.getText().toString());
|
||||||
viewBinding.markerEditPhoto.setImageBitmap(null);
|
|
||||||
photoUriDeleteList.add(photoUri);
|
|
||||||
photoUriOriginal = photoUriOriginal == null ? photoUri : photoUriOriginal;
|
|
||||||
photoUri = null;
|
|
||||||
hideAndShowOptions();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
viewBinding.markerEditCancel.setOnClickListener(v -> {
|
viewBinding.markerEditCancel.setOnClickListener(v -> {
|
||||||
Track.Id trackId = getTrackId();
|
viewModel.onCancel();
|
||||||
if (isNewMarker && trackId != null) {
|
|
||||||
// new marker and user cancel -> delete all photos from track directory
|
|
||||||
for (Uri photoUriDelete : photoUriDeleteList) {
|
|
||||||
File photoFile = FileUtils.getPhotoFileIfExists(this, trackId, photoUriDelete);
|
|
||||||
FileUtils.deleteDirectoryRecurse(photoFile);
|
|
||||||
}
|
|
||||||
if (photoUri != null) {
|
|
||||||
File photoFile = FileUtils.getPhotoFileIfExists(this, trackId, photoUri);
|
|
||||||
FileUtils.deleteDirectoryRecurse(photoFile);
|
|
||||||
}
|
|
||||||
} else if (!isNewMarker && trackId != null) {
|
|
||||||
// no new marker, user cancel and photo was changed -> delete all photos but original one
|
|
||||||
for (Uri photoUri : photoUriDeleteList) {
|
|
||||||
if (photoUriOriginal != photoUri) {
|
|
||||||
File photoFile = FileUtils.getPhotoFileIfExists(this, trackId, photoUri);
|
|
||||||
FileUtils.deleteDirectoryRecurse(photoFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (photoUri != null && photoUri != photoUriOriginal) {
|
|
||||||
File photoFile = FileUtils.getPhotoFileIfExists(this, trackId, photoUri);
|
|
||||||
FileUtils.deleteDirectoryRecurse(photoFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finish();
|
finish();
|
||||||
});
|
});
|
||||||
|
|
||||||
isNewMarker = markerId == null;
|
boolean isNewMarker = markerId == null;
|
||||||
|
|
||||||
setTitle(isNewMarker ? R.string.menu_insert_marker : R.string.menu_edit);
|
setTitle(isNewMarker ? R.string.menu_insert_marker : R.string.menu_edit);
|
||||||
viewBinding.markerEditDone.setText(isNewMarker ? R.string.generic_add : R.string.generic_save);
|
viewBinding.markerEditDone.setText(isNewMarker ? R.string.generic_add : R.string.generic_save);
|
||||||
viewBinding.markerEditDone.setOnClickListener(v -> {
|
viewBinding.markerEditDone.setOnClickListener(v -> {
|
||||||
if (isNewMarker) {
|
viewModel.onDone(viewBinding.markerEditName.getText().toString(),
|
||||||
addMarker();
|
viewBinding.markerEditMarkerType.getText().toString(),
|
||||||
} else {
|
viewBinding.markerEditDescription.getText().toString());
|
||||||
saveMarker();
|
|
||||||
}
|
|
||||||
Track.Id trackId = getTrackId();
|
|
||||||
if (trackId == null) {
|
|
||||||
for (Uri photoUri : photoUriDeleteList) {
|
|
||||||
File photoFile = FileUtils.getPhotoFileIfExists(this, trackId, photoUri);
|
|
||||||
FileUtils.deleteDirectoryRecurse(photoFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finish();
|
finish();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isNewMarker) {
|
viewModel = new ViewModelProvider(this).get(MarkerEditViewModel.class);
|
||||||
int nextMarkerNumber = trackId == null ? -1 : new ContentProviderUtils(this).getNextMarkerNumber(trackId);
|
viewModel.getMarkerData(trackId, markerId).observe(this, data -> {
|
||||||
if (nextMarkerNumber == -1) {
|
marker = data;
|
||||||
nextMarkerNumber = 0;
|
|
||||||
}
|
|
||||||
viewBinding.markerEditName.setText(getString(R.string.marker_name_format, nextMarkerNumber));
|
|
||||||
viewBinding.markerEditName.selectAll();
|
|
||||||
viewBinding.markerEditMarkerType.setText("");
|
|
||||||
viewBinding.markerEditDescription.setText("");
|
|
||||||
} else {
|
|
||||||
marker = new ContentProviderUtils(this).getMarker(markerId);
|
|
||||||
if (marker == null) {
|
|
||||||
Log.d(TAG, "marker is null");
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
viewBinding.markerEditName.setText(marker.getName());
|
viewBinding.markerEditName.setText(marker.getName());
|
||||||
viewBinding.markerEditMarkerType.setText(marker.getCategory());
|
viewBinding.markerEditMarkerType.setText(marker.getCategory());
|
||||||
viewBinding.markerEditDescription.setText(marker.getDescription());
|
viewBinding.markerEditDescription.setText(marker.getDescription());
|
||||||
if (marker.hasPhoto()) {
|
if (marker.hasPhoto()) {
|
||||||
photoUri = marker.getPhotoURI();
|
setMarkerImageView(marker.getPhotoURI());
|
||||||
}
|
} else {
|
||||||
}
|
viewBinding.markerEditPhoto.setImageDrawable(null);
|
||||||
|
|
||||||
if (savedInstanceState != null) {
|
|
||||||
photoUri = savedInstanceState.getParcelable(BUNDLE_PHOTO_URI);
|
|
||||||
if (marker != null) {
|
|
||||||
marker.setPhotoUrl(photoUri != null ? photoUri.toString() : null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (photoUri != null) {
|
|
||||||
setMarkerImageView(photoUri);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hideAndShowOptions();
|
hideAndShowOptions();
|
||||||
}
|
});
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
trackRecordingServiceConnection.startConnection(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
outState.putParcelable(BUNDLE_PHOTO_URI, photoUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStop() {
|
|
||||||
super.onStop();
|
|
||||||
trackRecordingServiceConnection.unbind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -227,16 +141,17 @@ public class MarkerEditActivity extends AbstractActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
if (item.getItemId() == R.id.marker_edit_insert_photo) {
|
||||||
case R.id.marker_edit_insert_photo:
|
|
||||||
createMarkerWithPicture();
|
createMarkerWithPicture();
|
||||||
return true;
|
return true;
|
||||||
case R.id.marker_edit_insert_gallery_img:
|
}
|
||||||
|
|
||||||
|
if (item.getItemId() == R.id.marker_edit_insert_gallery_img) {
|
||||||
createMarkerWithGalleryImage();
|
createMarkerWithGalleryImage();
|
||||||
return true;
|
return true;
|
||||||
default:
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -246,26 +161,20 @@ public class MarkerEditActivity extends AbstractActivity {
|
|||||||
Toast.makeText(this, R.string.marker_add_photo_canceled, Toast.LENGTH_LONG).show();
|
Toast.makeText(this, R.string.marker_add_photo_canceled, Toast.LENGTH_LONG).show();
|
||||||
return;
|
return;
|
||||||
} else if (resultCode == RESULT_OK) {
|
} else if (resultCode == RESULT_OK) {
|
||||||
setMarkerImageView(photoUri);
|
viewModel.onNewCameraPhoto(cameraPhotoUri,
|
||||||
|
viewBinding.markerEditName.getText().toString(),
|
||||||
|
viewBinding.markerEditMarkerType.getText().toString(),
|
||||||
|
viewBinding.markerEditDescription.getText().toString());
|
||||||
}
|
}
|
||||||
} else if (requestCode == GALLERY_IMG_REQUEST_CODE) {
|
} else if (requestCode == GALLERY_IMG_REQUEST_CODE) {
|
||||||
if (resultCode == RESULT_CANCELED) {
|
if (resultCode == RESULT_CANCELED) {
|
||||||
Toast.makeText(this, R.string.marker_add_photo_canceled, Toast.LENGTH_LONG).show();
|
Toast.makeText(this, R.string.marker_add_photo_canceled, Toast.LENGTH_LONG).show();
|
||||||
return;
|
return;
|
||||||
} else if (resultCode == RESULT_OK) {
|
} else if (resultCode == RESULT_OK) {
|
||||||
Uri srcUri = data.getData();
|
viewModel.onNewGalleryPhoto(data.getData(),
|
||||||
try (ParcelFileDescriptor parcelFd = getContentResolver().openFileDescriptor(srcUri, "r")) {
|
viewBinding.markerEditName.getText().toString(),
|
||||||
FileDescriptor srcFd = parcelFd.getFileDescriptor();
|
viewBinding.markerEditMarkerType.getText().toString(),
|
||||||
File dstFile = new File(FileUtils.getImageUrl(this, getTrackId()));
|
viewBinding.markerEditDescription.getText().toString());
|
||||||
FileUtils.copy(srcFd, dstFile);
|
|
||||||
|
|
||||||
photoUri = FileUtils.getUriForFile(this, dstFile);
|
|
||||||
setMarkerImageView(photoUri);
|
|
||||||
} catch(Exception e) {
|
|
||||||
Log.e(TAG, e.getMessage());
|
|
||||||
Toast.makeText(this, R.string.marker_add_canceled, Toast.LENGTH_LONG).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
@@ -273,11 +182,11 @@ public class MarkerEditActivity extends AbstractActivity {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks and hide/shows all buttons/options about marker photo options.
|
* Checks and hide/shows all buttons/options about marker photo options.
|
||||||
*
|
* <p>
|
||||||
* If a photo is set then one's options are shown, otherwise another ones are shown.
|
* If a photo is set then one's options are shown, otherwise another ones are shown.
|
||||||
*/
|
*/
|
||||||
private void hideAndShowOptions() {
|
private void hideAndShowOptions() {
|
||||||
boolean isPhotoSet = (marker != null && marker.hasPhoto()) || photoUri != null;
|
boolean isPhotoSet = (marker != null && marker.hasPhoto());
|
||||||
if (insertPhotoMenuItem != null && insertGalleryImgMenuItem != null) {
|
if (insertPhotoMenuItem != null && insertGalleryImgMenuItem != null) {
|
||||||
insertPhotoMenuItem.setVisible(!isPhotoSet);
|
insertPhotoMenuItem.setVisible(!isPhotoSet);
|
||||||
insertGalleryImgMenuItem.setVisible(!isPhotoSet);
|
insertGalleryImgMenuItem.setVisible(!isPhotoSet);
|
||||||
@@ -306,7 +215,7 @@ public class MarkerEditActivity extends AbstractActivity {
|
|||||||
|
|
||||||
private void createMarkerWithPicture() {
|
private void createMarkerWithPicture() {
|
||||||
Pair<Intent, Uri> intentAndPhotoUri = IntentUtils.createTakePictureIntent(this, getTrackId());
|
Pair<Intent, Uri> intentAndPhotoUri = IntentUtils.createTakePictureIntent(this, getTrackId());
|
||||||
photoUri = intentAndPhotoUri.second;
|
cameraPhotoUri = intentAndPhotoUri.second;
|
||||||
startActivityForResult(intentAndPhotoUri.first, CAMERA_REQUEST_CODE);
|
startActivityForResult(intentAndPhotoUri.first, CAMERA_REQUEST_CODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,21 +223,4 @@ public class MarkerEditActivity extends AbstractActivity {
|
|||||||
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
|
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
|
||||||
startActivityForResult(intent, GALLERY_IMG_REQUEST_CODE);
|
startActivityForResult(intent, GALLERY_IMG_REQUEST_CODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addMarker() {
|
|
||||||
trackRecordingServiceConnection.addMarker(this,
|
|
||||||
viewBinding.markerEditName.getText().toString(),
|
|
||||||
viewBinding.markerEditMarkerType.getText().toString(),
|
|
||||||
viewBinding.markerEditDescription.getText().toString(),
|
|
||||||
photoUri != null ? photoUri.toString() : null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saveMarker() {
|
|
||||||
marker.setName(viewBinding.markerEditName.getText().toString());
|
|
||||||
marker.setCategory(viewBinding.markerEditMarkerType.getText().toString());
|
|
||||||
marker.setDescription(viewBinding.markerEditDescription.getText().toString());
|
|
||||||
marker.setPhotoUrl(photoUri != null ? photoUri.toString() : null);
|
|
||||||
|
|
||||||
new ContentProviderUtils(this).updateMarker(this, marker);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
package de.dennisguse.opentracks;
|
package de.dennisguse.opentracks;
|
||||||
|
|
||||||
import android.app.SearchManager;
|
import android.app.SearchManager;
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||||
@@ -30,11 +29,12 @@ import android.view.View;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.widget.SearchView;
|
import androidx.appcompat.widget.SearchView;
|
||||||
import androidx.cursoradapter.widget.ResourceCursorAdapter;
|
|
||||||
import androidx.loader.app.LoaderManager;
|
import androidx.loader.app.LoaderManager;
|
||||||
import androidx.loader.content.CursorLoader;
|
import androidx.loader.content.CursorLoader;
|
||||||
import androidx.loader.content.Loader;
|
import androidx.loader.content.Loader;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.adapters.MarkerResourceCursorAdapter;
|
||||||
|
import de.dennisguse.opentracks.adapters.ScrollVisibleViews;
|
||||||
import de.dennisguse.opentracks.content.data.Marker;
|
import de.dennisguse.opentracks.content.data.Marker;
|
||||||
import de.dennisguse.opentracks.content.data.MarkerColumns;
|
import de.dennisguse.opentracks.content.data.MarkerColumns;
|
||||||
import de.dennisguse.opentracks.content.data.Track;
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
@@ -44,8 +44,6 @@ import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment;
|
|||||||
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
|
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
|
||||||
import de.dennisguse.opentracks.util.ActivityUtils;
|
import de.dennisguse.opentracks.util.ActivityUtils;
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
import de.dennisguse.opentracks.util.ListItemUtils;
|
|
||||||
import de.dennisguse.opentracks.util.MarkerUtils;
|
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -81,7 +79,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
private Track track;
|
private Track track;
|
||||||
private ResourceCursorAdapter resourceCursorAdapter;
|
private MarkerResourceCursorAdapter resourceCursorAdapter;
|
||||||
|
|
||||||
private MarkerListBinding viewBinding;
|
private MarkerListBinding viewBinding;
|
||||||
|
|
||||||
@@ -93,14 +91,11 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
public void onPrepare(Menu menu, int[] positions, long[] ids, boolean showSelectAll) {
|
public void onPrepare(Menu menu, int[] positions, long[] ids, boolean showSelectAll) {
|
||||||
boolean isSingleSelection = ids.length == 1;
|
boolean isSingleSelection = ids.length == 1;
|
||||||
|
|
||||||
menu.findItem(R.id.list_context_menu_share).setVisible(false);
|
|
||||||
menu.findItem(R.id.list_context_menu_show_on_map).setVisible(isSingleSelection);
|
menu.findItem(R.id.list_context_menu_show_on_map).setVisible(isSingleSelection);
|
||||||
menu.findItem(R.id.list_context_menu_edit).setVisible(isSingleSelection);
|
menu.findItem(R.id.list_context_menu_edit).setVisible(isSingleSelection);
|
||||||
menu.findItem(R.id.list_context_menu_delete).setVisible(true);
|
menu.findItem(R.id.list_context_menu_delete).setVisible(true);
|
||||||
/*
|
|
||||||
* Set select all to the same visibility as delete since delete is the
|
// Set select all to the same visibility as delete since delete is the only action that can be applied to multiple markers.
|
||||||
* only action that can be applied to multiple markers.
|
|
||||||
*/
|
|
||||||
menu.findItem(R.id.list_context_menu_select_all).setVisible(showSelectAll);
|
menu.findItem(R.id.list_context_menu_select_all).setVisible(showSelectAll);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,33 +121,17 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
|
|
||||||
viewBinding.markerList.setEmptyView(viewBinding.markerListEmpty);
|
viewBinding.markerList.setEmptyView(viewBinding.markerListEmpty);
|
||||||
viewBinding.markerList.setOnItemClickListener((parent, view, position, id) -> {
|
viewBinding.markerList.setOnItemClickListener((parent, view, position, id) -> {
|
||||||
|
resourceCursorAdapter.markerInvalid(id);
|
||||||
Intent intent = IntentUtils.newIntent(MarkerListActivity.this, MarkerDetailActivity.class)
|
Intent intent = IntentUtils.newIntent(MarkerListActivity.this, MarkerDetailActivity.class)
|
||||||
.putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, new Marker.Id(id));
|
.putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, new Marker.Id(id));
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
});
|
});
|
||||||
resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) {
|
|
||||||
@Override
|
|
||||||
public void bindView(View view, Context context, Cursor cursor) {
|
|
||||||
int nameIndex = cursor.getColumnIndex(MarkerColumns.NAME);
|
|
||||||
int timeIndex = cursor.getColumnIndexOrThrow(MarkerColumns.TIME);
|
|
||||||
int categoryIndex = cursor.getColumnIndex(MarkerColumns.CATEGORY);
|
|
||||||
int descriptionIndex = cursor.getColumnIndex(MarkerColumns.DESCRIPTION);
|
|
||||||
int photoUrlIndex = cursor.getColumnIndex(MarkerColumns.PHOTOURL);
|
|
||||||
|
|
||||||
int iconId = MarkerUtils.ICON_ID;
|
resourceCursorAdapter = new MarkerResourceCursorAdapter(this, R.layout.list_item);
|
||||||
String name = cursor.getString(nameIndex);
|
ScrollVisibleViews scrollVisibleViews = new ScrollVisibleViews(resourceCursorAdapter);
|
||||||
long time = cursor.getLong(timeIndex);
|
viewBinding.markerList.setOnScrollListener(scrollVisibleViews);
|
||||||
String category = cursor.getString(categoryIndex);
|
|
||||||
String description = cursor.getString(descriptionIndex);
|
|
||||||
String photoUrl = cursor.getString(photoUrlIndex);
|
|
||||||
|
|
||||||
ListItemUtils.setListItem(MarkerListActivity.this, view, false, true, iconId, R.string.image_marker, name, null, null, 0, time, false, category, description, photoUrl);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
viewBinding.markerList.setAdapter(resourceCursorAdapter);
|
viewBinding.markerList.setAdapter(resourceCursorAdapter);
|
||||||
ActivityUtils.configureListViewContextualMenu(viewBinding.markerList, contextualActionModeCallback);
|
ActivityUtils.configureListViewContextualMenu(viewBinding.markerList, contextualActionModeCallback);
|
||||||
|
|
||||||
loadData(getIntent());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -166,6 +145,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
this.invalidateOptionsMenu();
|
this.invalidateOptionsMenu();
|
||||||
|
loadData(getIntent());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -174,6 +154,16 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
resourceCursorAdapter.clear();
|
||||||
|
viewBinding = null;
|
||||||
|
resourceCursorAdapter = null;
|
||||||
|
contentProviderUtils = null;
|
||||||
|
sharedPreferences = null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected View getRootView() {
|
protected View getRootView() {
|
||||||
viewBinding = MarkerListBinding.inflate(getLayoutInflater());
|
viewBinding = MarkerListBinding.inflate(getLayoutInflater());
|
||||||
@@ -222,31 +212,45 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
markerIds[i] = new Marker.Id(longMarkerIds[i]);
|
markerIds[i] = new Marker.Id(longMarkerIds[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Intent intent;
|
if (itemId == R.id.list_context_menu_show_on_map) {
|
||||||
switch (itemId) {
|
|
||||||
case R.id.list_context_menu_show_on_map:
|
|
||||||
if (markerIds.length == 1) {
|
if (markerIds.length == 1) {
|
||||||
IntentUtils.showCoordinateOnMap(this, contentProviderUtils.getMarker(markerIds[0]));
|
IntentUtils.showCoordinateOnMap(this, contentProviderUtils.getMarker(markerIds[0]));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
case R.id.list_context_menu_edit:
|
}
|
||||||
|
|
||||||
|
if (itemId == R.id.list_context_menu_share) {
|
||||||
|
Intent intent = IntentUtils.newShareFileIntent(this, markerIds);
|
||||||
|
if (intent != null) {
|
||||||
|
intent = Intent.createChooser(intent, null);
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemId == R.id.list_context_menu_edit) {
|
||||||
if (markerIds.length == 1) {
|
if (markerIds.length == 1) {
|
||||||
intent = IntentUtils.newIntent(this, MarkerEditActivity.class)
|
resourceCursorAdapter.markerInvalid(markerIds[0].getId());
|
||||||
|
Intent intent = IntentUtils.newIntent(this, MarkerEditActivity.class)
|
||||||
.putExtra(MarkerEditActivity.EXTRA_MARKER_ID, markerIds[0]);
|
.putExtra(MarkerEditActivity.EXTRA_MARKER_ID, markerIds[0]);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
case R.id.list_context_menu_delete:
|
}
|
||||||
|
|
||||||
|
if (itemId == R.id.list_context_menu_delete) {
|
||||||
DeleteMarkerDialogFragment.showDialog(getSupportFragmentManager(), markerIds);
|
DeleteMarkerDialogFragment.showDialog(getSupportFragmentManager(), markerIds);
|
||||||
return true;
|
return true;
|
||||||
case R.id.list_context_menu_select_all:
|
}
|
||||||
|
|
||||||
|
if (itemId == R.id.list_context_menu_select_all) {
|
||||||
for (int i = 0; i < viewBinding.markerList.getCount(); i++) {
|
for (int i = 0; i < viewBinding.markerList.getCount(); i++) {
|
||||||
viewBinding.markerList.setItemChecked(i, true);
|
viewBinding.markerList.setItemChecked(i, true);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import android.os.Handler;
|
|||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.accessibility.AccessibilityEvent;
|
import android.view.accessibility.AccessibilityEvent;
|
||||||
import android.widget.ImageButton;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.databinding.TrackControllerBinding;
|
import de.dennisguse.opentracks.databinding.TrackControllerBinding;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||||
@@ -103,7 +102,7 @@ public class TrackController implements View.OnTouchListener {
|
|||||||
if (buttonDelay != null && event.getAction() == MotionEvent.ACTION_UP) {
|
if (buttonDelay != null && event.getAction() == MotionEvent.ACTION_UP) {
|
||||||
handlerUpdateTotalTime.removeCallbacks(buttonDelay);
|
handlerUpdateTotalTime.removeCallbacks(buttonDelay);
|
||||||
buttonDelay = null;
|
buttonDelay = null;
|
||||||
((ImageButton) view).setPressed(false);
|
view.setPressed(false);
|
||||||
|
|
||||||
transition.resetTransition();
|
transition.resetTransition();
|
||||||
|
|
||||||
@@ -113,7 +112,7 @@ public class TrackController implements View.OnTouchListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Trigger button pressed animation
|
//Trigger button pressed animation
|
||||||
((ImageButton) view).setPressed(true);
|
view.setPressed(true);
|
||||||
|
|
||||||
//To pause a recording
|
//To pause a recording
|
||||||
if (viewBinding.trackControllerRecord.equals(view)
|
if (viewBinding.trackControllerRecord.equals(view)
|
||||||
|
|||||||
@@ -78,6 +78,12 @@ public class TrackDeleteActivity extends AbstractActivity {
|
|||||||
deleteThread.interrupt();
|
deleteThread.interrupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
viewBinding = null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected View getRootView() {
|
protected View getRootView() {
|
||||||
viewBinding = TrackDeleteBinding.inflate(getLayoutInflater());
|
viewBinding = TrackDeleteBinding.inflate(getLayoutInflater());
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
|||||||
|
|
||||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection();
|
trackRecordingServiceConnection = new TrackRecordingServiceConnection();
|
||||||
Track.Id trackId = getIntent().getParcelableExtra(EXTRA_TRACK_ID);
|
Track.Id trackId = getIntent().getParcelableExtra(EXTRA_TRACK_ID);
|
||||||
if (!trackId.isValid()) {
|
if (trackId == null) {
|
||||||
Log.e(TAG, "invalid trackId");
|
Log.e(TAG, "invalid trackId");
|
||||||
finish();
|
finish();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import android.widget.Toast;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.widget.SearchView;
|
import androidx.appcompat.widget.SearchView;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.core.app.ActivityCompat;
|
import androidx.core.app.ActivityCompat;
|
||||||
import androidx.cursoradapter.widget.ResourceCursorAdapter;
|
import androidx.cursoradapter.widget.ResourceCursorAdapter;
|
||||||
import androidx.loader.app.LoaderManager;
|
import androidx.loader.app.LoaderManager;
|
||||||
@@ -232,7 +233,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
|
|
||||||
ListItemUtils.setListItem(TrackListActivity.this, view, isRecording, recordingTrackPaused,
|
ListItemUtils.setListItem(TrackListActivity.this, view, isRecording, recordingTrackPaused,
|
||||||
iconId, R.string.image_track, name, totalTime, totalDistance, markerCount,
|
iconId, R.string.image_track, name, totalTime, totalDistance, markerCount,
|
||||||
startTime, true, category, description, null);
|
startTime, true, category, description, false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
viewBinding.trackList.setAdapter(resourceCursorAdapter);
|
viewBinding.trackList.setAdapter(resourceCursorAdapter);
|
||||||
@@ -243,6 +244,12 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
requestGPSPermissions();
|
requestGPSPermissions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setupActionBarBack(Toolbar toolbar) {
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
toolbar.setNavigationIcon(R.drawable.ic_logo_color_24dp);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStart() {
|
protected void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
@@ -320,13 +327,10 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
Intent intent;
|
if (item.getItemId() == R.id.track_list_start_gps) {
|
||||||
switch (item.getItemId()) {
|
|
||||||
case R.id.track_list_start_gps:
|
|
||||||
LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
|
LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
|
||||||
if (locationManager != null && !locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
|
if (locationManager != null && !locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
|
||||||
intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
|
startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
|
||||||
startActivity(intent);
|
|
||||||
} else {
|
} else {
|
||||||
// Invoke trackRecordingService
|
// Invoke trackRecordingService
|
||||||
if (!isGpsStarted()) {
|
if (!isGpsStarted()) {
|
||||||
@@ -343,20 +347,25 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
// Update menu after starting or stopping gps
|
// Update menu after starting or stopping gps
|
||||||
this.invalidateOptionsMenu();
|
this.invalidateOptionsMenu();
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
case R.id.track_list_aggregated_stats:
|
|
||||||
intent = IntentUtils.newIntent(this, AggregatedStatisticsActivity.class);
|
|
||||||
startActivity(intent);
|
|
||||||
return true;
|
|
||||||
case R.id.track_list_markers:
|
|
||||||
intent = IntentUtils.newIntent(this, MarkerListActivity.class);
|
|
||||||
startActivity(intent);
|
|
||||||
return true;
|
|
||||||
case R.id.track_list_settings:
|
|
||||||
intent = IntentUtils.newIntent(this, SettingsActivity.class);
|
|
||||||
startActivity(intent);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (item.getItemId() == R.id.track_list_aggregated_stats) {
|
||||||
|
startActivity(IntentUtils.newIntent(this, AggregatedStatisticsActivity.class));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.getItemId() == R.id.track_list_markers) {
|
||||||
|
startActivity(IntentUtils.newIntent(this, MarkerListActivity.class));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.getItemId() == R.id.track_list_settings) {
|
||||||
|
startActivity(IntentUtils.newIntent(this, SettingsActivity.class));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -467,31 +476,37 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
trackIds[i] = new Track.Id(longTrackIds[i]);
|
trackIds[i] = new Track.Id(longTrackIds[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Intent intent;
|
if (itemId == R.id.list_context_menu_show_on_map) {
|
||||||
switch (itemId) {
|
|
||||||
case R.id.list_context_menu_show_on_map:
|
|
||||||
IntentDashboardUtils.startDashboard(this, false, trackIds);
|
IntentDashboardUtils.startDashboard(this, false, trackIds);
|
||||||
return true;
|
return true;
|
||||||
case R.id.list_context_menu_share:
|
}
|
||||||
intent = IntentUtils.newShareFileIntent(this, trackIds);
|
|
||||||
|
if (itemId == R.id.list_context_menu_share) {
|
||||||
|
Intent intent = IntentUtils.newShareFileIntent(this, trackIds);
|
||||||
intent = Intent.createChooser(intent, null);
|
intent = Intent.createChooser(intent, null);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
case R.id.list_context_menu_edit:
|
}
|
||||||
intent = IntentUtils.newIntent(this, TrackEditActivity.class)
|
|
||||||
|
|
||||||
|
if (itemId == R.id.list_context_menu_edit) {
|
||||||
|
Intent intent = IntentUtils.newIntent(this, TrackEditActivity.class)
|
||||||
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackIds[0]);
|
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackIds[0]);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
case R.id.list_context_menu_delete:
|
}
|
||||||
|
|
||||||
|
if (itemId == R.id.list_context_menu_delete) {
|
||||||
deleteTracks(trackIds);
|
deleteTracks(trackIds);
|
||||||
return true;
|
return true;
|
||||||
case R.id.list_context_menu_select_all:
|
}
|
||||||
int size = viewBinding.trackList.getCount();
|
if (itemId == R.id.list_context_menu_select_all) {
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < viewBinding.trackList.getCount(); i++) {
|
||||||
viewBinding.trackList.setItemChecked(i, true);
|
viewBinding.trackList.setItemChecked(i, true);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,8 +25,10 @@ import android.view.View;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
import androidx.fragment.app.FragmentPagerAdapter;
|
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||||
|
|
||||||
|
import com.google.android.material.tabs.TabLayoutMediator;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.TrackDataHub;
|
import de.dennisguse.opentracks.content.TrackDataHub;
|
||||||
import de.dennisguse.opentracks.content.data.Marker;
|
import de.dennisguse.opentracks.content.data.Marker;
|
||||||
@@ -75,8 +77,10 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
|
|||||||
|
|
||||||
trackDataHub = new TrackDataHub(this);
|
trackDataHub = new TrackDataHub(this);
|
||||||
|
|
||||||
viewBinding.trackDetailActivityViewPager.setAdapter(new CustomFragmentPagerAdapter(getSupportFragmentManager(), FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT));
|
CustomFragmentPagerAdapter pagerAdapter = new CustomFragmentPagerAdapter(this);
|
||||||
viewBinding.trackDetailActivityTablayout.setupWithViewPager(viewBinding.trackDetailActivityViewPager);
|
viewBinding.trackDetailActivityViewPager.setAdapter(pagerAdapter);
|
||||||
|
new TabLayoutMediator(viewBinding.trackDetailActivityTablayout, viewBinding.trackDetailActivityViewPager,
|
||||||
|
(tab, position) -> tab.setText(pagerAdapter.getPageTitle(position))).attach();
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
viewBinding.trackDetailActivityViewPager.setCurrentItem(savedInstanceState.getInt(CURRENT_TAB_TAG_KEY));
|
viewBinding.trackDetailActivityViewPager.setCurrentItem(savedInstanceState.getInt(CURRENT_TAB_TAG_KEY));
|
||||||
}
|
}
|
||||||
@@ -142,43 +146,51 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
Intent intent;
|
if (item.getItemId() == R.id.track_detail_share) {
|
||||||
switch (item.getItemId()) {
|
Intent intent = Intent.createChooser(IntentUtils.newShareFileIntent(this, trackId), null);
|
||||||
case R.id.track_detail_share:
|
|
||||||
intent = IntentUtils.newShareFileIntent(this, trackId);
|
|
||||||
intent = Intent.createChooser(intent, null);
|
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
case R.id.track_detail_menu_show_on_map:
|
}
|
||||||
|
|
||||||
|
if (item.getItemId() == R.id.track_detail_menu_show_on_map) {
|
||||||
IntentDashboardUtils.startDashboard(this, false, trackId);
|
IntentDashboardUtils.startDashboard(this, false, trackId);
|
||||||
return true;
|
return true;
|
||||||
case R.id.track_detail_markers:
|
}
|
||||||
intent = IntentUtils.newIntent(this, MarkerListActivity.class)
|
|
||||||
|
if (item.getItemId() == R.id.track_detail_markers) {
|
||||||
|
Intent intent = IntentUtils.newIntent(this, MarkerListActivity.class)
|
||||||
.putExtra(MarkerListActivity.EXTRA_TRACK_ID, trackId);
|
.putExtra(MarkerListActivity.EXTRA_TRACK_ID, trackId);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
case R.id.track_detail_edit:
|
}
|
||||||
intent = IntentUtils.newIntent(this, TrackEditActivity.class)
|
|
||||||
|
if (item.getItemId() == R.id.track_detail_edit) {
|
||||||
|
Intent intent = IntentUtils.newIntent(this, TrackEditActivity.class)
|
||||||
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId);
|
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
case R.id.track_detail_delete:
|
}
|
||||||
|
|
||||||
|
if (item.getItemId() == R.id.track_detail_delete) {
|
||||||
deleteTracks(trackId);
|
deleteTracks(trackId);
|
||||||
return true;
|
return true;
|
||||||
case R.id.track_detail_resume_track:
|
}
|
||||||
|
|
||||||
|
if (item.getItemId() == R.id.track_detail_resume_track) {
|
||||||
Intent newIntent = IntentUtils.newIntent(TrackRecordedActivity.this, TrackRecordingActivity.class)
|
Intent newIntent = IntentUtils.newIntent(TrackRecordedActivity.this, TrackRecordingActivity.class)
|
||||||
.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, trackId);
|
.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, trackId);
|
||||||
startActivity(newIntent);
|
startActivity(newIntent);
|
||||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
|
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
|
||||||
finish();
|
finish();
|
||||||
return true;
|
return true;
|
||||||
case R.id.track_detail_settings:
|
|
||||||
intent = IntentUtils.newIntent(this, SettingsActivity.class);
|
|
||||||
startActivity(intent);
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (item.getItemId() == R.id.track_detail_settings) {
|
||||||
|
startActivity(IntentUtils.newIntent(this, SettingsActivity.class));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -220,20 +232,15 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class CustomFragmentPagerAdapter extends FragmentPagerAdapter {
|
private class CustomFragmentPagerAdapter extends FragmentStateAdapter {
|
||||||
|
|
||||||
public CustomFragmentPagerAdapter(@NonNull FragmentManager fm, int behavior) {
|
public CustomFragmentPagerAdapter(@NonNull FragmentActivity fa) {
|
||||||
super(fm, behavior);
|
super(fa);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getCount() {
|
|
||||||
return 4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Fragment getItem(int position) {
|
public Fragment createFragment(int position) {
|
||||||
switch (position) {
|
switch (position) {
|
||||||
case 0:
|
case 0:
|
||||||
return StatisticsRecordedFragment.newInstance(trackId);
|
return StatisticsRecordedFragment.newInstance(trackId);
|
||||||
@@ -249,6 +256,10 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
public CharSequence getPageTitle(int position) {
|
public CharSequence getPageTitle(int position) {
|
||||||
switch (position) {
|
switch (position) {
|
||||||
case 0:
|
case 0:
|
||||||
|
|||||||
@@ -13,8 +13,10 @@ import android.view.WindowManager;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
import androidx.fragment.app.FragmentPagerAdapter;
|
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||||
|
|
||||||
|
import com.google.android.material.tabs.TabLayoutMediator;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.TrackDataHub;
|
import de.dennisguse.opentracks.content.TrackDataHub;
|
||||||
import de.dennisguse.opentracks.content.data.Track;
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
@@ -148,8 +150,10 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
||||||
trackDataHub = new TrackDataHub(this);
|
trackDataHub = new TrackDataHub(this);
|
||||||
|
|
||||||
viewBinding.trackDetailActivityViewPager.setAdapter(new CustomFragmentPagerAdapter(getSupportFragmentManager(), FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT));
|
CustomFragmentPagerAdapter pagerAdapter = new CustomFragmentPagerAdapter(this);
|
||||||
viewBinding.trackDetailActivityTablayout.setupWithViewPager(viewBinding.trackDetailActivityViewPager);
|
viewBinding.trackDetailActivityViewPager.setAdapter(pagerAdapter);
|
||||||
|
new TabLayoutMediator(viewBinding.trackDetailActivityTablayout, viewBinding.trackDetailActivityViewPager,
|
||||||
|
(tab, position) -> tab.setText(pagerAdapter.getPageTitle(position))).attach();
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
viewBinding.trackDetailActivityViewPager.setCurrentItem(savedInstanceState.getInt(CURRENT_TAB_TAG_KEY));
|
viewBinding.trackDetailActivityViewPager.setCurrentItem(savedInstanceState.getInt(CURRENT_TAB_TAG_KEY));
|
||||||
}
|
}
|
||||||
@@ -274,34 +278,41 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
Intent intent;
|
if (item.getItemId() == R.id.track_detail_insert_marker) {
|
||||||
switch (item.getItemId()) {
|
Intent intent = IntentUtils
|
||||||
case R.id.track_detail_insert_marker:
|
|
||||||
intent = IntentUtils
|
|
||||||
.newIntent(this, MarkerEditActivity.class)
|
.newIntent(this, MarkerEditActivity.class)
|
||||||
.putExtra(MarkerEditActivity.EXTRA_TRACK_ID, trackId);
|
.putExtra(MarkerEditActivity.EXTRA_TRACK_ID, trackId);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
case R.id.track_detail_menu_show_on_map:
|
}
|
||||||
|
|
||||||
|
if (item.getItemId() == R.id.track_detail_menu_show_on_map) {
|
||||||
IntentDashboardUtils.startDashboard(this, true, trackId);
|
IntentDashboardUtils.startDashboard(this, true, trackId);
|
||||||
return true;
|
return true;
|
||||||
case R.id.track_detail_markers:
|
}
|
||||||
intent = IntentUtils.newIntent(this, MarkerListActivity.class)
|
|
||||||
|
if (item.getItemId() == R.id.track_detail_markers) {
|
||||||
|
Intent intent = IntentUtils.newIntent(this, MarkerListActivity.class)
|
||||||
.putExtra(MarkerListActivity.EXTRA_TRACK_ID, trackId);
|
.putExtra(MarkerListActivity.EXTRA_TRACK_ID, trackId);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
case R.id.track_detail_edit:
|
|
||||||
intent = IntentUtils.newIntent(this, TrackEditActivity.class)
|
}
|
||||||
|
|
||||||
|
if (item.getItemId() == R.id.track_detail_edit) {
|
||||||
|
Intent intent = IntentUtils.newIntent(this, TrackEditActivity.class)
|
||||||
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId);
|
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
case R.id.track_detail_settings:
|
}
|
||||||
intent = IntentUtils.newIntent(this, SettingsActivity.class);
|
|
||||||
|
if (item.getItemId() == R.id.track_detail_settings) {
|
||||||
|
Intent intent = IntentUtils.newIntent(this, SettingsActivity.class);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
default:
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -358,20 +369,15 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class CustomFragmentPagerAdapter extends FragmentPagerAdapter {
|
private class CustomFragmentPagerAdapter extends FragmentStateAdapter {
|
||||||
|
|
||||||
public CustomFragmentPagerAdapter(@NonNull FragmentManager fm, int behavior) {
|
public CustomFragmentPagerAdapter(@NonNull FragmentActivity fa) {
|
||||||
super(fm, behavior);
|
super(fa);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getCount() {
|
|
||||||
return 4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Fragment getItem(int position) {
|
public Fragment createFragment(int position) {
|
||||||
switch (position) {
|
switch (position) {
|
||||||
case 0:
|
case 0:
|
||||||
return StatisticsRecordingFragment.newInstance();
|
return StatisticsRecordingFragment.newInstance();
|
||||||
@@ -387,6 +393,10 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
public CharSequence getPageTitle(int position) {
|
public CharSequence getPageTitle(int position) {
|
||||||
switch (position) {
|
switch (position) {
|
||||||
case 0:
|
case 0:
|
||||||
|
|||||||
@@ -46,49 +46,45 @@ public class AggregatedStatisticsAdapter extends BaseAdapter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View getView(int position, View convertView, ViewGroup parent) {
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
AggregatedStatistics.AggregatedStatistic aggregatedStats = getItem(position);
|
AggregatedStatistics.AggregatedStatistic aggregatedStatistic = getItem(position);
|
||||||
ViewHolder viewHolder;
|
|
||||||
|
|
||||||
if (isSpeedSport(position)) {
|
ViewHolder viewHolder;
|
||||||
viewHolder = new ViewSpeedHolder();
|
if (convertView == null) {
|
||||||
|
convertView = LayoutInflater.from(context).inflate(R.layout.aggregated_stats_list_item, parent, false);
|
||||||
|
viewHolder = new ViewHolder(convertView);
|
||||||
|
convertView.setTag(viewHolder);
|
||||||
} else {
|
} else {
|
||||||
viewHolder = new ViewPaceHolder();
|
viewHolder = (ViewHolder) convertView.getTag();
|
||||||
}
|
}
|
||||||
|
|
||||||
convertView = LayoutInflater.from(context).inflate(R.layout.aggregated_stats_list_item, parent, false);
|
if (TrackIconUtils.isSpeedIcon(context, aggregatedStatistic.getCategory())) {
|
||||||
viewHolder.setValues(convertView, getIcon(position), getName(position), aggregatedStats);
|
viewHolder.setSpeed(aggregatedStatistic);
|
||||||
|
} else {
|
||||||
|
viewHolder.setPace(aggregatedStatistic);
|
||||||
|
}
|
||||||
|
|
||||||
return convertView;
|
return convertView;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isSpeedSport(int position) {
|
|
||||||
String category = aggregatedStatistics.getSportName(position);
|
|
||||||
return TrackIconUtils.isSpeedIcon(context, category);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getIcon(int position) {
|
|
||||||
String iconValue = TrackIconUtils.getIconValue(context, aggregatedStatistics.getSportName(position));
|
|
||||||
return TrackIconUtils.getIconDrawable(iconValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getName(int position) {
|
|
||||||
return aggregatedStatistics.getSportName(position);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ViewHolder {
|
private class ViewHolder {
|
||||||
protected ImageView sportIcon;
|
private final ImageView sportIcon;
|
||||||
protected TextView typeLabel;
|
private final TextView typeLabel;
|
||||||
protected TextView numTracks;
|
private final TextView numTracks;
|
||||||
protected TextView distance;
|
private final TextView distance;
|
||||||
protected TextView distanceUnit;
|
private final TextView distanceUnit;
|
||||||
protected TextView time;
|
private final TextView time;
|
||||||
protected boolean metricsUnits;
|
|
||||||
protected boolean reportSpeed;
|
|
||||||
|
|
||||||
public void setValues(View view, int iconDrawable, String name, AggregatedStatistics.AggregatedStatistic aggregatedStats) {
|
private final TextView avgSpeed;
|
||||||
reportSpeed = PreferencesUtils.isReportSpeed(context, name);
|
private final TextView avgSpeedUnit;
|
||||||
metricsUnits = PreferencesUtils.isMetricUnits(context);
|
private final TextView avgSpeedLabel;
|
||||||
|
private final TextView maxSpeed;
|
||||||
|
private final TextView maxSpeedUnit;
|
||||||
|
private final TextView maxSpeedLabel;
|
||||||
|
|
||||||
|
private boolean metricsUnits;
|
||||||
|
private boolean reportSpeed;
|
||||||
|
|
||||||
|
public ViewHolder(View view) {
|
||||||
sportIcon = view.findViewById(R.id.aggregated_stats_sport_icon);
|
sportIcon = view.findViewById(R.id.aggregated_stats_sport_icon);
|
||||||
typeLabel = view.findViewById(R.id.aggregated_stats_type_label);
|
typeLabel = view.findViewById(R.id.aggregated_stats_type_label);
|
||||||
numTracks = view.findViewById(R.id.aggregated_stats_num_tracks);
|
numTracks = view.findViewById(R.id.aggregated_stats_num_tracks);
|
||||||
@@ -96,86 +92,68 @@ public class AggregatedStatisticsAdapter extends BaseAdapter {
|
|||||||
distanceUnit = view.findViewById(R.id.aggregated_stats_distance_unit);
|
distanceUnit = view.findViewById(R.id.aggregated_stats_distance_unit);
|
||||||
time = view.findViewById(R.id.aggregated_stats_time);
|
time = view.findViewById(R.id.aggregated_stats_time);
|
||||||
|
|
||||||
sportIcon.setImageResource(iconDrawable);
|
|
||||||
typeLabel.setText(name);
|
|
||||||
numTracks.setText(StringUtils.valueInParentheses(String.valueOf(aggregatedStats.getCountTracks())));
|
|
||||||
|
|
||||||
Pair<String, String> parts = StringUtils.getDistanceParts(context, aggregatedStats.getTrackStatistics().getTotalDistance(), metricsUnits);
|
|
||||||
distance.setText(parts.first);
|
|
||||||
distanceUnit.setText(parts.second);
|
|
||||||
|
|
||||||
time.setText(StringUtils.formatElapsedTime(aggregatedStats.getTrackStatistics().getMovingTime()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ViewSpeedHolder extends ViewHolder {
|
|
||||||
private TextView avgSpeed;
|
|
||||||
private TextView avgSpeedUnit;
|
|
||||||
private TextView avgSpeedLabel;
|
|
||||||
private TextView maxSpeed;
|
|
||||||
private TextView maxSpeedUnit;
|
|
||||||
private TextView maxSpeedLabel;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setValues(View view, int iconDrawable, String name, AggregatedStatistics.AggregatedStatistic aggregatedStats) {
|
|
||||||
super.setValues(view, iconDrawable, name, aggregatedStats);
|
|
||||||
|
|
||||||
avgSpeed = view.findViewById(R.id.aggregated_stats_avg_rate);
|
avgSpeed = view.findViewById(R.id.aggregated_stats_avg_rate);
|
||||||
avgSpeedUnit = view.findViewById(R.id.aggregated_stats_avg_rate_unit);
|
avgSpeedUnit = view.findViewById(R.id.aggregated_stats_avg_rate_unit);
|
||||||
avgSpeedLabel = view.findViewById(R.id.aggregated_stats_avg_rate_label);
|
avgSpeedLabel = view.findViewById(R.id.aggregated_stats_avg_rate_label);
|
||||||
maxSpeed = view.findViewById(R.id.aggregated_stats_max_rate);
|
maxSpeed = view.findViewById(R.id.aggregated_stats_max_rate);
|
||||||
maxSpeedUnit = view.findViewById(R.id.aggregated_stats_max_rate_unit);
|
maxSpeedUnit = view.findViewById(R.id.aggregated_stats_max_rate_unit);
|
||||||
maxSpeedLabel = view.findViewById(R.id.aggregated_stats_max_rate_label);
|
maxSpeedLabel = view.findViewById(R.id.aggregated_stats_max_rate_label);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSpeed(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {
|
||||||
|
setCommonValues(aggregatedStatistic);
|
||||||
{
|
{
|
||||||
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStats.getTrackStatistics().getAverageMovingSpeed(), metricsUnits, reportSpeed);
|
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStatistic.getTrackStatistics().getAverageMovingSpeed(), metricsUnits, reportSpeed);
|
||||||
avgSpeed.setText(parts.first);
|
avgSpeed.setText(parts.first);
|
||||||
avgSpeedUnit.setText(parts.second);
|
avgSpeedUnit.setText(parts.second);
|
||||||
avgSpeedLabel.setText(context.getString(R.string.stats_average_moving_speed));
|
avgSpeedLabel.setText(context.getString(R.string.stats_average_moving_speed));
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStats.getTrackStatistics().getMaxSpeed(), metricsUnits, reportSpeed);
|
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStatistic.getTrackStatistics().getMaxSpeed(), metricsUnits, reportSpeed);
|
||||||
maxSpeed.setText(parts.first);
|
maxSpeed.setText(parts.first);
|
||||||
maxSpeedUnit.setText(parts.second);
|
maxSpeedUnit.setText(parts.second);
|
||||||
maxSpeedLabel.setText(context.getString(R.string.stats_max_speed));
|
maxSpeedLabel.setText(context.getString(R.string.stats_max_speed));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private class ViewPaceHolder extends ViewHolder {
|
|
||||||
private TextView avgPace;
|
|
||||||
private TextView avgPaceUnit;
|
|
||||||
private TextView avgPaceLabel;
|
|
||||||
private TextView maxPace;
|
|
||||||
private TextView maxPaceUnit;
|
|
||||||
private TextView maxPaceLabel;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setValues(View view, int iconDrawable, String name, AggregatedStatistics.AggregatedStatistic aggregatedStats) {
|
|
||||||
super.setValues(view, iconDrawable, name, aggregatedStats);
|
|
||||||
|
|
||||||
avgPace = view.findViewById(R.id.aggregated_stats_avg_rate);
|
|
||||||
avgPaceUnit = view.findViewById(R.id.aggregated_stats_avg_rate_unit);
|
|
||||||
avgPaceLabel = view.findViewById(R.id.aggregated_stats_avg_rate_label);
|
|
||||||
|
|
||||||
maxPace = view.findViewById(R.id.aggregated_stats_max_rate);
|
|
||||||
maxPaceUnit = view.findViewById(R.id.aggregated_stats_max_rate_unit);
|
|
||||||
maxPaceLabel = view.findViewById(R.id.aggregated_stats_max_rate_label);
|
|
||||||
|
|
||||||
|
public void setPace(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {
|
||||||
|
setCommonValues(aggregatedStatistic);
|
||||||
{
|
{
|
||||||
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStats.getTrackStatistics().getAverageMovingSpeed(), metricsUnits, reportSpeed);
|
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStatistic.getTrackStatistics().getAverageMovingSpeed(), metricsUnits, reportSpeed);
|
||||||
avgPace.setText(parts.first);
|
avgSpeed.setText(parts.first);
|
||||||
avgPaceUnit.setText(parts.second);
|
avgSpeedUnit.setText(parts.second);
|
||||||
avgPaceLabel.setText(R.string.stats_average_moving_pace);
|
avgSpeedLabel.setText(context.getString(R.string.stats_average_moving_pace));
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStats.getTrackStatistics().getMaxSpeed(), metricsUnits, reportSpeed);
|
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStatistic.getTrackStatistics().getMaxSpeed(), metricsUnits, reportSpeed);
|
||||||
maxPace.setText(parts.first);
|
maxSpeed.setText(parts.first);
|
||||||
maxPaceUnit.setText(parts.second);
|
maxSpeedUnit.setText(parts.second);
|
||||||
maxPaceLabel.setText(R.string.stats_fastest_pace);
|
maxSpeedLabel.setText(R.string.stats_fastest_pace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setCommonValues(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {
|
||||||
|
String category = aggregatedStatistic.getCategory();
|
||||||
|
|
||||||
|
reportSpeed = PreferencesUtils.isReportSpeed(context, category);
|
||||||
|
metricsUnits = PreferencesUtils.isMetricUnits(context);
|
||||||
|
|
||||||
|
sportIcon.setImageResource(getIcon(aggregatedStatistic));
|
||||||
|
typeLabel.setText(category);
|
||||||
|
numTracks.setText(StringUtils.valueInParentheses(String.valueOf(aggregatedStatistic.getCountTracks())));
|
||||||
|
|
||||||
|
Pair<String, String> parts = StringUtils.getDistanceParts(context, aggregatedStatistic.getTrackStatistics().getTotalDistance(), metricsUnits);
|
||||||
|
distance.setText(parts.first);
|
||||||
|
distanceUnit.setText(parts.second);
|
||||||
|
|
||||||
|
time.setText(StringUtils.formatElapsedTime(aggregatedStatistic.getTrackStatistics().getMovingTime()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getIcon(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {
|
||||||
|
String iconValue = TrackIconUtils.getIconValue(context, aggregatedStatistic.getCategory());
|
||||||
|
return TrackIconUtils.getIconDrawable(iconValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package de.dennisguse.opentracks.adapters;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ExecutorService wrap that avoid the execution of the same object in a ListView.
|
||||||
|
*
|
||||||
|
* @param <T> the type of the object that will be used to identified the Runnable.
|
||||||
|
*/
|
||||||
|
class ExecutorListViewService<T> {
|
||||||
|
|
||||||
|
private List<T> enqueueObjects = new ArrayList<>();
|
||||||
|
private ExecutorService executorService;
|
||||||
|
|
||||||
|
public ExecutorListViewService(int numThreads) {
|
||||||
|
executorService = Executors.newFixedThreadPool(numThreads);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void shutdown() {
|
||||||
|
enqueueObjects.clear();
|
||||||
|
enqueueObjects = null;
|
||||||
|
executorService.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the runnable for the object.
|
||||||
|
*
|
||||||
|
* @param object the Object.
|
||||||
|
* @param runnable the Runnable.
|
||||||
|
*/
|
||||||
|
public void execute(T object, Runnable runnable) {
|
||||||
|
if (!preExecute(object)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
new Thread(() -> {
|
||||||
|
Future future = executorService.submit(runnable);
|
||||||
|
try {
|
||||||
|
future.get();
|
||||||
|
} catch (ExecutionException | InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
postExecute(object);
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Before execution it must checks if the object is already enqueued.
|
||||||
|
*
|
||||||
|
* @param object the object to check.
|
||||||
|
* @return true if it can be executed or false otherwise.
|
||||||
|
*/
|
||||||
|
private boolean preExecute(T object) {
|
||||||
|
synchronized (enqueueObjects) {
|
||||||
|
if (!enqueueObjects.contains(object)) {
|
||||||
|
enqueueObjects.add(object);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* After execution remove the object from the queue.
|
||||||
|
*
|
||||||
|
* @param object the object to be removed from the queue.
|
||||||
|
*/
|
||||||
|
private void postExecute(T object) {
|
||||||
|
synchronized (enqueueObjects) {
|
||||||
|
enqueueObjects.remove(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package de.dennisguse.opentracks.adapters;
|
package de.dennisguse.opentracks.adapters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.Pair;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -20,10 +19,9 @@ import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
|
|||||||
|
|
||||||
public class IntervalStatisticsAdapter extends ArrayAdapter<IntervalStatistics.Interval> {
|
public class IntervalStatisticsAdapter extends ArrayAdapter<IntervalStatistics.Interval> {
|
||||||
|
|
||||||
private StackMode stackMode;
|
private final StackMode stackMode;
|
||||||
private boolean metricUnits;
|
private final boolean metricUnits;
|
||||||
private float sumDistance_m;
|
private final String category;
|
||||||
private String category;
|
|
||||||
|
|
||||||
public IntervalStatisticsAdapter(Context context, List<IntervalStatistics.Interval> intervalList, String category, StackMode stackMode) {
|
public IntervalStatisticsAdapter(Context context, List<IntervalStatistics.Interval> intervalList, String category, StackMode stackMode) {
|
||||||
super(context, R.layout.interval_stats_list_item, intervalList);
|
super(context, R.layout.interval_stats_list_item, intervalList);
|
||||||
@@ -47,12 +45,14 @@ public class IntervalStatisticsAdapter extends ArrayAdapter<IntervalStatistics.I
|
|||||||
viewHolder.distance = intervalView.findViewById(R.id.interval_item_distance);
|
viewHolder.distance = intervalView.findViewById(R.id.interval_item_distance);
|
||||||
viewHolder.rate = intervalView.findViewById(R.id.interval_item_rate);
|
viewHolder.rate = intervalView.findViewById(R.id.interval_item_rate);
|
||||||
viewHolder.gain = intervalView.findViewById(R.id.interval_item_gain);
|
viewHolder.gain = intervalView.findViewById(R.id.interval_item_gain);
|
||||||
|
viewHolder.loss = intervalView.findViewById(R.id.interval_item_loss);
|
||||||
|
|
||||||
intervalView.setTag(viewHolder);
|
intervalView.setTag(viewHolder);
|
||||||
} else {
|
} else {
|
||||||
viewHolder = (ViewHolder) intervalView.getTag();
|
viewHolder = (ViewHolder) intervalView.getTag();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float sumDistance_m;
|
||||||
if (actualPosition + 1 == getCount() && actualPosition > 0) {
|
if (actualPosition + 1 == getCount() && actualPosition > 0) {
|
||||||
sumDistance_m = actualPosition * getItem(actualPosition - 1).getDistance_m() + interval.getDistance_m();
|
sumDistance_m = actualPosition * getItem(actualPosition - 1).getDistance_m() + interval.getDistance_m();
|
||||||
} else {
|
} else {
|
||||||
@@ -61,15 +61,13 @@ public class IntervalStatisticsAdapter extends ArrayAdapter<IntervalStatistics.I
|
|||||||
viewHolder.distance.setText(StringUtils.formatDistance(getContext(), sumDistance_m, metricUnits));
|
viewHolder.distance.setText(StringUtils.formatDistance(getContext(), sumDistance_m, metricUnits));
|
||||||
|
|
||||||
if (PreferencesUtils.isReportSpeed(getContext(), category)) {
|
if (PreferencesUtils.isReportSpeed(getContext(), category)) {
|
||||||
Pair<String, String> speedParts = StringUtils.getSpeedParts(getContext(), interval.getSpeed_ms(), metricUnits, true);
|
viewHolder.rate.setText(StringUtils.formatSpeed(getContext(), interval.getSpeed_ms(), metricUnits, true));
|
||||||
viewHolder.rate.setText(speedParts.first + " " + speedParts.second);
|
|
||||||
} else {
|
} else {
|
||||||
Pair<String, String> paceParts = StringUtils.getSpeedParts(getContext(), interval.getSpeed_ms(), metricUnits, false);
|
viewHolder.rate.setText(StringUtils.formatSpeed(getContext(), interval.getSpeed_ms(), metricUnits, false));
|
||||||
viewHolder.rate.setText(paceParts.first + " " + paceParts.second);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Pair<String, String> gainParts = StringUtils.formatElevation(getContext(), interval.getGain_m(), metricUnits);
|
viewHolder.gain.setText(StringUtils.formatDistance(getContext(), interval.getGain_m(), metricUnits));
|
||||||
viewHolder.gain.setText(gainParts.first + " " + gainParts.second);
|
viewHolder.loss.setText(StringUtils.formatDistance(getContext(), interval.getLoss_m(), metricUnits));
|
||||||
|
|
||||||
return intervalView;
|
return intervalView;
|
||||||
}
|
}
|
||||||
@@ -86,5 +84,6 @@ public class IntervalStatisticsAdapter extends ArrayAdapter<IntervalStatistics.I
|
|||||||
private TextView distance;
|
private TextView distance;
|
||||||
private TextView rate;
|
private TextView rate;
|
||||||
private TextView gain;
|
private TextView gain;
|
||||||
|
private TextView loss;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,184 @@
|
|||||||
|
package de.dennisguse.opentracks.adapters;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.media.ThumbnailUtils;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.collection.LruCache;
|
||||||
|
import androidx.cursoradapter.widget.ResourceCursorAdapter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.R;
|
||||||
|
import de.dennisguse.opentracks.content.data.MarkerColumns;
|
||||||
|
import de.dennisguse.opentracks.util.ListItemUtils;
|
||||||
|
import de.dennisguse.opentracks.util.MarkerUtils;
|
||||||
|
|
||||||
|
public class MarkerResourceCursorAdapter extends ResourceCursorAdapter implements ScrollVisibleViews.VisibleViewsListener {
|
||||||
|
|
||||||
|
private static final String TAG = MarkerResourceCursorAdapter.class.getSimpleName();
|
||||||
|
|
||||||
|
private static final int LIST_PREFERRED_ITEM_HEIGHT_DEFAULT = 128;
|
||||||
|
|
||||||
|
private final Activity activity;
|
||||||
|
|
||||||
|
private ExecutorListViewService executorService = new ExecutorListViewService<Long>(LIST_PREFERRED_ITEM_HEIGHT_DEFAULT);
|
||||||
|
|
||||||
|
private boolean scroll = false;
|
||||||
|
|
||||||
|
// Cache size is in bytes.
|
||||||
|
private final LruCache<String, Bitmap> memoryCache;
|
||||||
|
|
||||||
|
public MarkerResourceCursorAdapter(Activity activity, int layout) {
|
||||||
|
super(activity, layout, null, 0);
|
||||||
|
|
||||||
|
this.activity = activity;
|
||||||
|
|
||||||
|
// Get max available VM memory, exceeding this amount will throw an OutOfMemory exception.
|
||||||
|
final long usedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
|
||||||
|
final int cacheSize = (int) (Runtime.getRuntime().maxMemory() - usedMemory) / 8;
|
||||||
|
|
||||||
|
memoryCache = new LruCache<String, Bitmap>(cacheSize) {
|
||||||
|
@Override
|
||||||
|
protected int sizeOf(@NonNull String key, @NonNull Bitmap bitmap) {
|
||||||
|
return bitmap.getByteCount();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void bindView(View view, Context context, Cursor cursor) {
|
||||||
|
int idIndex = cursor.getColumnIndex(MarkerColumns._ID);
|
||||||
|
int nameIndex = cursor.getColumnIndex(MarkerColumns.NAME);
|
||||||
|
int timeIndex = cursor.getColumnIndexOrThrow(MarkerColumns.TIME);
|
||||||
|
int categoryIndex = cursor.getColumnIndex(MarkerColumns.CATEGORY);
|
||||||
|
int descriptionIndex = cursor.getColumnIndex(MarkerColumns.DESCRIPTION);
|
||||||
|
int photoUrlIndex = cursor.getColumnIndex(MarkerColumns.PHOTOURL);
|
||||||
|
|
||||||
|
long id = cursor.getLong(idIndex);
|
||||||
|
int iconId = MarkerUtils.ICON_ID;
|
||||||
|
String name = cursor.getString(nameIndex);
|
||||||
|
long time = cursor.getLong(timeIndex);
|
||||||
|
String category = cursor.getString(categoryIndex);
|
||||||
|
String description = cursor.getString(descriptionIndex);
|
||||||
|
String photoUrl = cursor.getString(photoUrlIndex);
|
||||||
|
|
||||||
|
view.setTag(String.valueOf(id));
|
||||||
|
|
||||||
|
boolean hasPhoto = photoUrl != null && !photoUrl.equals("");
|
||||||
|
|
||||||
|
ImageView imageView = view.findViewById(R.id.list_item_photo);
|
||||||
|
ImageView textGradient = view.findViewById(R.id.list_item_text_gradient);
|
||||||
|
imageView.setVisibility(hasPhoto ? View.VISIBLE : View.GONE);
|
||||||
|
textGradient.setVisibility(hasPhoto ? View.VISIBLE : View.GONE);
|
||||||
|
imageView.setImageBitmap(null);
|
||||||
|
|
||||||
|
if (hasPhoto) {
|
||||||
|
int height = getPhotoHeight(activity);
|
||||||
|
ViewGroup.LayoutParams params = imageView.getLayoutParams();
|
||||||
|
params.height = height;
|
||||||
|
imageView.setLayoutParams(params);
|
||||||
|
|
||||||
|
if (getBitmapFromMemCache(String.valueOf(id)) != null || !scroll) {
|
||||||
|
asyncLoadPhoto(view, imageView, photoUrl, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ListItemUtils.setListItem(activity, view, false, true, iconId, R.string.image_marker, name, null, null, 0, time, false, category, description, hasPhoto);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
executorService.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewVisible(View view, int position) {
|
||||||
|
scroll = true;
|
||||||
|
|
||||||
|
Cursor cursor = getCursor();
|
||||||
|
if (!cursor.moveToPosition(position)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long id = cursor.getLong(cursor.getColumnIndex(MarkerColumns._ID));
|
||||||
|
String photoUrl = cursor.getString(cursor.getColumnIndex(MarkerColumns.PHOTOURL));
|
||||||
|
|
||||||
|
boolean hasPhoto = photoUrl != null && !photoUrl.equals("");
|
||||||
|
if (hasPhoto) {
|
||||||
|
ImageView imageView = view.findViewById(R.id.list_item_photo);
|
||||||
|
asyncLoadPhoto(view, imageView, photoUrl, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markerInvalid(long id) {
|
||||||
|
memoryCache.remove(String.valueOf(id));
|
||||||
|
scroll = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addBitmapToMemoryCache(String key, Bitmap bitmap) {
|
||||||
|
synchronized (memoryCache) {
|
||||||
|
if (getBitmapFromMemCache(key) == null) {
|
||||||
|
memoryCache.put(key, bitmap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bitmap getBitmapFromMemCache(String key) {
|
||||||
|
return memoryCache.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It loads the photoUrl in the imageView from view.
|
||||||
|
* It takes the photo from cache or from storage if isn't in the cache.
|
||||||
|
*
|
||||||
|
* @param view item's view.
|
||||||
|
* @param imageView view object where photo will be loaded.
|
||||||
|
* @param photoUrl photo's url.
|
||||||
|
* @param id marker's id where photo belong.
|
||||||
|
*/
|
||||||
|
private void asyncLoadPhoto(View view, ImageView imageView, String photoUrl, long id) {
|
||||||
|
Bitmap photo = getBitmapFromMemCache(String.valueOf(id));
|
||||||
|
imageView.setImageBitmap(photo);
|
||||||
|
|
||||||
|
if (photo == null) {
|
||||||
|
executorService.execute(id, () -> {
|
||||||
|
try (InputStream inputStream = activity.getContentResolver().openInputStream(Uri.parse(photoUrl))) {
|
||||||
|
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
|
||||||
|
int height = getPhotoHeight(activity);
|
||||||
|
Log.d(TAG, "Width : " + (bitmap.getWidth() / (bitmap.getHeight() / height)) + " | Height: " + height);
|
||||||
|
final Bitmap finalPhoto = ThumbnailUtils.extractThumbnail(bitmap, bitmap.getWidth() / (bitmap.getHeight() / height), height);
|
||||||
|
addBitmapToMemoryCache(String.valueOf(id), finalPhoto);
|
||||||
|
if (view.getTag().equals(String.valueOf(id))) {
|
||||||
|
activity.runOnUiThread(() -> imageView.setImageBitmap(finalPhoto));
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Failed to image " + e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the photo height.
|
||||||
|
*
|
||||||
|
* @param context the context
|
||||||
|
*/
|
||||||
|
private static int getPhotoHeight(Context context) {
|
||||||
|
int[] attrs = new int[]{android.R.attr.listPreferredItemHeight};
|
||||||
|
TypedArray typeArray = context.obtainStyledAttributes(attrs);
|
||||||
|
int height = typeArray.getDimensionPixelSize(0, LIST_PREFERRED_ITEM_HEIGHT_DEFAULT);
|
||||||
|
typeArray.recycle();
|
||||||
|
return 2 * height;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package de.dennisguse.opentracks.adapters;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.AbsListView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AbsListView.OnScrollListener class that can be used to know what views in a ListView are currently visible while scrolling.
|
||||||
|
*/
|
||||||
|
public class ScrollVisibleViews implements AbsListView.OnScrollListener {
|
||||||
|
private int from = -1;
|
||||||
|
private int to = -1;
|
||||||
|
|
||||||
|
private VisibleViewsListener visibleViewsListener;
|
||||||
|
|
||||||
|
public ScrollVisibleViews(@NonNull VisibleViewsListener visibleViewsListener) {
|
||||||
|
this.visibleViewsListener = visibleViewsListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScrollStateChanged(AbsListView view, int scrollState) {
|
||||||
|
if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE || scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING || scrollState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
|
||||||
|
if (from >= 0 && to >= 0) {
|
||||||
|
for (int i = from; i < to; i++) {
|
||||||
|
View viewChild = view.getChildAt(i - from);
|
||||||
|
visibleViewsListener.onViewVisible(viewChild, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
|
||||||
|
from = firstVisibleItem;
|
||||||
|
to = firstVisibleItem + visibleItemCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface VisibleViewsListener {
|
||||||
|
void onViewVisible(View view, int position);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -76,10 +76,10 @@ public class SensorsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class ViewHolder extends RecyclerView.ViewHolder {
|
private class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
TextView label;
|
final TextView label;
|
||||||
TextView sensorValue;
|
final TextView sensorValue;
|
||||||
TextView value;
|
final TextView value;
|
||||||
TextView unit;
|
final TextView unit;
|
||||||
|
|
||||||
public ViewHolder(@NonNull View itemView) {
|
public ViewHolder(@NonNull View itemView) {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ abstract class ChartValueSeries {
|
|||||||
* @param fillColor the fill color
|
* @param fillColor the fill color
|
||||||
* @param strokeColor the stroke color
|
* @param strokeColor the stroke color
|
||||||
*/
|
*/
|
||||||
ChartValueSeries(Context context, int absoluteMin, int absoluteMax, int[] intervalValues, int metricTitleId, int imperialTitleId, int fillColor, int strokeColor) {
|
ChartValueSeries(Context context, int absoluteMin, int absoluteMax, int[] intervalValues, int metricTitleId, int imperialTitleId, int fillColor, int strokeColor, int fontSizeSmall, int fontSizeMedium) {
|
||||||
this.absoluteMin = absoluteMin;
|
this.absoluteMin = absoluteMin;
|
||||||
this.absoluteMax = absoluteMax;
|
this.absoluteMax = absoluteMax;
|
||||||
this.intervalValues = intervalValues;
|
this.intervalValues = intervalValues;
|
||||||
@@ -85,16 +85,14 @@ abstract class ChartValueSeries {
|
|||||||
strokePaint.setColor(context.getResources().getColor(strokeColor));
|
strokePaint.setColor(context.getResources().getColor(strokeColor));
|
||||||
strokePaint.setAntiAlias(true);
|
strokePaint.setAntiAlias(true);
|
||||||
|
|
||||||
float scale = context.getResources().getDisplayMetrics().density;
|
|
||||||
|
|
||||||
// Make copies of the stroke paint with the default thickness
|
// Make copies of the stroke paint with the default thickness
|
||||||
titlePaint = new Paint(strokePaint);
|
titlePaint = new Paint(strokePaint);
|
||||||
titlePaint.setTextSize(ChartView.MEDIUM_TEXT_SIZE * scale);
|
titlePaint.setTextSize(fontSizeMedium);
|
||||||
titlePaint.setTextAlign(Align.CENTER);
|
titlePaint.setTextAlign(Align.CENTER);
|
||||||
titlePaint.setStyle(Style.FILL_AND_STROKE);
|
titlePaint.setStyle(Style.FILL_AND_STROKE);
|
||||||
|
|
||||||
markerPaint = new Paint(strokePaint);
|
markerPaint = new Paint(strokePaint);
|
||||||
markerPaint.setTextSize(ChartView.SMALL_TEXT_SIZE * scale);
|
markerPaint.setTextSize(fontSizeSmall);
|
||||||
markerPaint.setTextAlign(Align.RIGHT);
|
markerPaint.setTextAlign(Align.RIGHT);
|
||||||
markerPaint.setStyle(Style.FILL_AND_STROKE);
|
markerPaint.setStyle(Style.FILL_AND_STROKE);
|
||||||
|
|
||||||
|
|||||||
@@ -30,9 +30,11 @@ import android.view.GestureDetector;
|
|||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.ScaleGestureDetector;
|
import android.view.ScaleGestureDetector;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.ViewParent;
|
||||||
import android.widget.Scroller;
|
import android.widget.Scroller;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.core.view.GestureDetectorCompat;
|
import androidx.core.view.GestureDetectorCompat;
|
||||||
|
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
@@ -58,9 +60,6 @@ import de.dennisguse.opentracks.util.UnitConversions;
|
|||||||
*/
|
*/
|
||||||
public class ChartView extends View {
|
public class ChartView extends View {
|
||||||
|
|
||||||
static final float MEDIUM_TEXT_SIZE = 18f;
|
|
||||||
static final float SMALL_TEXT_SIZE = 12f;
|
|
||||||
|
|
||||||
static final int Y_AXIS_INTERVALS = 5;
|
static final int Y_AXIS_INTERVALS = 5;
|
||||||
|
|
||||||
private static final int TARGET_X_AXIS_INTERVALS = 4;
|
private static final int TARGET_X_AXIS_INTERVALS = 4;
|
||||||
@@ -114,7 +113,7 @@ public class ChartView extends View {
|
|||||||
private int effectiveWidth = 0;
|
private int effectiveWidth = 0;
|
||||||
private int effectiveHeight = 0;
|
private int effectiveHeight = 0;
|
||||||
|
|
||||||
private boolean chartByDistance;
|
private final boolean chartByDistance;
|
||||||
private boolean metricUnits = true;
|
private boolean metricUnits = true;
|
||||||
private boolean reportSpeed = true;
|
private boolean reportSpeed = true;
|
||||||
private boolean showPointer = false;
|
private boolean showPointer = false;
|
||||||
@@ -192,6 +191,9 @@ public class ChartView extends View {
|
|||||||
public ChartView(Context context, final boolean chartByDistance) {
|
public ChartView(Context context, final boolean chartByDistance) {
|
||||||
super(context);
|
super(context);
|
||||||
this.chartByDistance = chartByDistance;
|
this.chartByDistance = chartByDistance;
|
||||||
|
int fontSizeSmall = ThemeUtils.getFontSizeSmallInPx(context);
|
||||||
|
int fontSizeMedium = ThemeUtils.getFontSizeMediumInPx(context);
|
||||||
|
|
||||||
seriesList.add(new ChartValueSeries(context,
|
seriesList.add(new ChartValueSeries(context,
|
||||||
Integer.MIN_VALUE,
|
Integer.MIN_VALUE,
|
||||||
Integer.MAX_VALUE,
|
Integer.MAX_VALUE,
|
||||||
@@ -199,7 +201,9 @@ public class ChartView extends View {
|
|||||||
R.string.description_elevation_metric,
|
R.string.description_elevation_metric,
|
||||||
R.string.description_elevation_imperial,
|
R.string.description_elevation_imperial,
|
||||||
R.color.chart_elevation_fill,
|
R.color.chart_elevation_fill,
|
||||||
R.color.chart_elevation_border) {
|
R.color.chart_elevation_border,
|
||||||
|
fontSizeSmall,
|
||||||
|
fontSizeMedium) {
|
||||||
@Override
|
@Override
|
||||||
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||||
return chartPoint.getElevation();
|
return chartPoint.getElevation();
|
||||||
@@ -218,7 +222,9 @@ public class ChartView extends View {
|
|||||||
R.string.description_speed_metric,
|
R.string.description_speed_metric,
|
||||||
R.string.description_speed_imperial,
|
R.string.description_speed_imperial,
|
||||||
R.color.chart_speed_fill,
|
R.color.chart_speed_fill,
|
||||||
R.color.chart_speed_border) {
|
R.color.chart_speed_border,
|
||||||
|
fontSizeSmall,
|
||||||
|
fontSizeMedium) {
|
||||||
@Override
|
@Override
|
||||||
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||||
return chartPoint.getSpeed();
|
return chartPoint.getSpeed();
|
||||||
@@ -238,7 +244,9 @@ public class ChartView extends View {
|
|||||||
R.string.description_pace_metric,
|
R.string.description_pace_metric,
|
||||||
R.string.description_pace_imperial,
|
R.string.description_pace_imperial,
|
||||||
R.color.chart_pace_fill,
|
R.color.chart_pace_fill,
|
||||||
R.color.chart_pace_border) {
|
R.color.chart_pace_border,
|
||||||
|
fontSizeSmall,
|
||||||
|
fontSizeMedium) {
|
||||||
@Override
|
@Override
|
||||||
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||||
return chartPoint.getPace();
|
return chartPoint.getPace();
|
||||||
@@ -258,7 +266,9 @@ public class ChartView extends View {
|
|||||||
R.string.description_sensor_heart_rate,
|
R.string.description_sensor_heart_rate,
|
||||||
R.string.description_sensor_heart_rate,
|
R.string.description_sensor_heart_rate,
|
||||||
R.color.chart_heart_rate_fill,
|
R.color.chart_heart_rate_fill,
|
||||||
R.color.chart_heart_rate_border) {
|
R.color.chart_heart_rate_border,
|
||||||
|
fontSizeSmall,
|
||||||
|
fontSizeMedium) {
|
||||||
@Override
|
@Override
|
||||||
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||||
return chartPoint.getHeartRate();
|
return chartPoint.getHeartRate();
|
||||||
@@ -277,7 +287,9 @@ public class ChartView extends View {
|
|||||||
R.string.description_sensor_cadence,
|
R.string.description_sensor_cadence,
|
||||||
R.string.description_sensor_cadence,
|
R.string.description_sensor_cadence,
|
||||||
R.color.chart_cadence_fill,
|
R.color.chart_cadence_fill,
|
||||||
R.color.chart_cadence_border) {
|
R.color.chart_cadence_border,
|
||||||
|
fontSizeSmall,
|
||||||
|
fontSizeMedium) {
|
||||||
@Override
|
@Override
|
||||||
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||||
return chartPoint.getCadence();
|
return chartPoint.getCadence();
|
||||||
@@ -295,7 +307,9 @@ public class ChartView extends View {
|
|||||||
R.string.description_sensor_power,
|
R.string.description_sensor_power,
|
||||||
R.string.description_sensor_power,
|
R.string.description_sensor_power,
|
||||||
R.color.chart_power_fill,
|
R.color.chart_power_fill,
|
||||||
R.color.chart_power_border) {
|
R.color.chart_power_border,
|
||||||
|
fontSizeSmall,
|
||||||
|
fontSizeMedium) {
|
||||||
@Override
|
@Override
|
||||||
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||||
return chartPoint.getPower();
|
return chartPoint.getPower();
|
||||||
@@ -309,13 +323,11 @@ public class ChartView extends View {
|
|||||||
|
|
||||||
backgroundColor = ThemeUtils.getBackgroundColor(context);
|
backgroundColor = ThemeUtils.getBackgroundColor(context);
|
||||||
|
|
||||||
float scale = context.getResources().getDisplayMetrics().density;
|
|
||||||
|
|
||||||
axisPaint = new Paint();
|
axisPaint = new Paint();
|
||||||
axisPaint.setStyle(Style.FILL_AND_STROKE);
|
axisPaint.setStyle(Style.FILL_AND_STROKE);
|
||||||
axisPaint.setColor(ThemeUtils.getTextColorPrimary(context));
|
axisPaint.setColor(ThemeUtils.getTextColorPrimary(context));
|
||||||
axisPaint.setAntiAlias(true);
|
axisPaint.setAntiAlias(true);
|
||||||
axisPaint.setTextSize(SMALL_TEXT_SIZE * scale);
|
axisPaint.setTextSize(fontSizeSmall);
|
||||||
|
|
||||||
xAxisMarkerPaint = new Paint(axisPaint);
|
xAxisMarkerPaint = new Paint(axisPaint);
|
||||||
xAxisMarkerPaint.setTextAlign(Align.CENTER);
|
xAxisMarkerPaint.setTextAlign(Align.CENTER);
|
||||||
@@ -330,7 +342,7 @@ public class ChartView extends View {
|
|||||||
markerPaint.setStyle(Style.STROKE);
|
markerPaint.setStyle(Style.STROKE);
|
||||||
markerPaint.setAntiAlias(false);
|
markerPaint.setAntiAlias(false);
|
||||||
|
|
||||||
pointer = context.getResources().getDrawable(R.drawable.ic_logo_color_24dp);
|
pointer = ContextCompat.getDrawable(context, R.drawable.ic_logo_color_24dp);
|
||||||
pointer.setBounds(0, 0, pointer.getIntrinsicWidth(), pointer.getIntrinsicHeight());
|
pointer.setBounds(0, 0, pointer.getIntrinsicWidth(), pointer.getIntrinsicHeight());
|
||||||
|
|
||||||
markerPin = MarkerUtils.getDefaultPhoto(context);
|
markerPin = MarkerUtils.getDefaultPhoto(context);
|
||||||
@@ -489,6 +501,18 @@ public class ChartView extends View {
|
|||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle parent's view disallow touch event.
|
||||||
|
*
|
||||||
|
* @param disallow Does disallow parent touch event?
|
||||||
|
*/
|
||||||
|
private void requestDisallowInterceptTouchEventInParent(boolean disallow) {
|
||||||
|
ViewParent parent = getParent();
|
||||||
|
if (parent != null) {
|
||||||
|
parent.requestDisallowInterceptTouchEvent(disallow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scrolls the view horizontally by a given amount.
|
* Scrolls the view horizontally by a given amount.
|
||||||
*
|
*
|
||||||
@@ -496,13 +520,15 @@ public class ChartView extends View {
|
|||||||
*/
|
*/
|
||||||
private void scrollBy(int deltaX) {
|
private void scrollBy(int deltaX) {
|
||||||
int scrollX = getScrollX() + deltaX;
|
int scrollX = getScrollX() + deltaX;
|
||||||
if (scrollX < 0) {
|
if (scrollX <= 0) {
|
||||||
scrollX = 0;
|
scrollX = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int maxWidth = effectiveWidth * (zoomLevel - 1);
|
int maxWidth = effectiveWidth * (zoomLevel - 1);
|
||||||
if (scrollX > maxWidth) {
|
if (scrollX >= maxWidth) {
|
||||||
scrollX = maxWidth;
|
scrollX = maxWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollTo(scrollX, 0);
|
scrollTo(scrollX, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -527,6 +553,10 @@ public class ChartView extends View {
|
|||||||
public boolean onTouchEvent(MotionEvent event) {
|
public boolean onTouchEvent(MotionEvent event) {
|
||||||
boolean isZoom = detectorZoom.onTouchEvent(event);
|
boolean isZoom = detectorZoom.onTouchEvent(event);
|
||||||
boolean isScrollTab = detectorScrollFlingTab.onTouchEvent(event);
|
boolean isScrollTab = detectorScrollFlingTab.onTouchEvent(event);
|
||||||
|
|
||||||
|
// ChartView handles zoom gestures (more than one pointer) and all gestures when zoomed itself
|
||||||
|
requestDisallowInterceptTouchEventInParent(event.getPointerCount() != 1 || zoomLevel != MIN_ZOOM_LEVEL);
|
||||||
|
|
||||||
return isZoom || isScrollTab;
|
return isZoom || isScrollTab;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -881,6 +911,7 @@ public class ChartView extends View {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds the index of the first data point containing data for a series.
|
* Finds the index of the first data point containing data for a series.
|
||||||
|
*
|
||||||
* @return -1 if no data point contains data for the series.
|
* @return -1 if no data point contains data for the series.
|
||||||
*/
|
*/
|
||||||
private int getFirstPopulatedChartDataIndex(ChartValueSeries chartValueSeries) {
|
private int getFirstPopulatedChartDataIndex(ChartValueSeries chartValueSeries) {
|
||||||
|
|||||||
@@ -140,6 +140,11 @@ public class DescriptionGenerator {
|
|||||||
writeElevation(stats.getTotalElevationGain(), builder, R.string.description_elevation_gain, lineBreak);
|
writeElevation(stats.getTotalElevationGain(), builder, R.string.description_elevation_gain, lineBreak);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Elevation loss
|
||||||
|
if (stats.hasTotalElevationLoss()) {
|
||||||
|
writeElevation(stats.getTotalElevationLoss(), builder, R.string.description_elevation_loss, lineBreak);
|
||||||
|
}
|
||||||
|
|
||||||
// Recorded time
|
// Recorded time
|
||||||
builder.append(context.getString(R.string.description_recorded_time, StringUtils.formatDateTime(context, stats.getStartTime_ms())));
|
builder.append(context.getString(R.string.description_recorded_time, StringUtils.formatDateTime(context, stats.getStartTime_ms())));
|
||||||
builder.append(lineBreak);
|
builder.append(lineBreak);
|
||||||
|
|||||||
@@ -83,8 +83,8 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
|||||||
|
|
||||||
// Track points sampling state
|
// Track points sampling state
|
||||||
private int numLoadedPoints;
|
private int numLoadedPoints;
|
||||||
private long firstSeenTrackPointId;
|
private TrackPoint.Id firstSeenTrackPointId;
|
||||||
private long lastSeenTrackPointId;
|
private TrackPoint.Id lastSeenTrackPointId;
|
||||||
|
|
||||||
// Registered listeners
|
// Registered listeners
|
||||||
private ContentObserver tracksTableObserver;
|
private ContentObserver tracksTableObserver;
|
||||||
@@ -351,36 +351,42 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
|||||||
}
|
}
|
||||||
|
|
||||||
int localNumLoadedTrackPoints = updateSamplingState ? numLoadedPoints : 0;
|
int localNumLoadedTrackPoints = updateSamplingState ? numLoadedPoints : 0;
|
||||||
long localFirstSeenTrackPointId = updateSamplingState ? firstSeenTrackPointId : -1L;
|
TrackPoint.Id localFirstSeenTrackPointId = updateSamplingState ? firstSeenTrackPointId : null;
|
||||||
long localLastSeenTrackPointIdId = updateSamplingState ? lastSeenTrackPointId : -1L;
|
TrackPoint.Id localLastSeenTrackPointIdId = updateSamplingState ? lastSeenTrackPointId : null;
|
||||||
long maxPointId = updateSamplingState ? -1L : lastSeenTrackPointId;
|
TrackPoint.Id maxPointId = updateSamplingState ? null : lastSeenTrackPointId;
|
||||||
|
|
||||||
if (selectedTrackId == null) {
|
if (selectedTrackId == null) {
|
||||||
Log.w(TAG, "This should not happen, but it does"); //TODO
|
Log.w(TAG, "This should not happen, but it does"); //TODO
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
long lastTrackPointId = contentProviderUtils.getLastTrackPointId(selectedTrackId);
|
TrackPoint.Id lastTrackPointId = contentProviderUtils.getLastTrackPointId(selectedTrackId);
|
||||||
int samplingFrequency = -1;
|
int samplingFrequency = -1;
|
||||||
boolean includeNextPoint = false;
|
boolean includeNextPoint = false;
|
||||||
|
|
||||||
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId, localLastSeenTrackPointIdId + 1, false)) {
|
|
||||||
|
TrackPoint.Id next;
|
||||||
|
if (localLastSeenTrackPointIdId != null) {
|
||||||
|
next = new TrackPoint.Id(localLastSeenTrackPointIdId.getId() + 1); //TODO startTrackPointId + 1 is an assumption assumption; should be derived from the DB.
|
||||||
|
}
|
||||||
|
|
||||||
|
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId, null)) {
|
||||||
|
|
||||||
while (trackPointIterator.hasNext()) {
|
while (trackPointIterator.hasNext()) {
|
||||||
TrackPoint trackPoint = trackPointIterator.next();
|
TrackPoint trackPoint = trackPointIterator.next();
|
||||||
long trackPointId = trackPointIterator.getTrackPointId();
|
TrackPoint.Id trackPointId = trackPoint.getId();
|
||||||
|
|
||||||
// Stop if past the last wanted point
|
// Stop if past the last wanted point
|
||||||
if (maxPointId != -1L && trackPointId > maxPointId) {
|
if (maxPointId != null && trackPointId.getId() > maxPointId.getId()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (localFirstSeenTrackPointId == -1) {
|
if (localFirstSeenTrackPointId == null) {
|
||||||
localFirstSeenTrackPointId = trackPointId;
|
localFirstSeenTrackPointId = trackPointId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (samplingFrequency == -1) {
|
if (samplingFrequency == -1) {
|
||||||
long numTotalPoints = Math.max(0L, lastTrackPointId - localFirstSeenTrackPointId);
|
long numTotalPoints = Math.max(0L, lastTrackPointId.getId() - localFirstSeenTrackPointId.getId()); //TODO That is an assumption; should be derived from the DB.
|
||||||
samplingFrequency = 1 + (int) (numTotalPoints / targetNumPoints);
|
samplingFrequency = 1 + (int) (numTotalPoints / targetNumPoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,8 +427,8 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
|||||||
*/
|
*/
|
||||||
private void resetSamplingState() {
|
private void resetSamplingState() {
|
||||||
numLoadedPoints = 0;
|
numLoadedPoints = 0;
|
||||||
firstSeenTrackPointId = -1L;
|
firstSeenTrackPointId = null;
|
||||||
lastSeenTrackPointId = -1L;
|
lastSeenTrackPointId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ import androidx.annotation.VisibleForTesting;
|
|||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Leif Hendrik Wilden
|
* @author Leif Hendrik Wilden
|
||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
@@ -48,6 +46,11 @@ public final class Marker {
|
|||||||
@Deprecated //TODO Make an URI instead of String
|
@Deprecated //TODO Make an URI instead of String
|
||||||
private String photoUrl = "";
|
private String photoUrl = "";
|
||||||
|
|
||||||
|
public Marker(@Nullable Track.Id trackId) {
|
||||||
|
this.trackId = trackId;
|
||||||
|
location = null;
|
||||||
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public Marker(@NonNull Track.Id trackId, @NonNull TrackPoint trackPoint) {
|
public Marker(@NonNull Track.Id trackId, @NonNull TrackPoint trackPoint) {
|
||||||
this(trackId, trackPoint.getLocation());
|
this(trackId, trackPoint.getLocation());
|
||||||
@@ -79,8 +82,8 @@ public final class Marker {
|
|||||||
/**
|
/**
|
||||||
* May be null if the it was not loaded from the database.
|
* May be null if the it was not loaded from the database.
|
||||||
*/
|
*/
|
||||||
public @Nullable
|
@Nullable
|
||||||
Id getId() {
|
public Id getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,8 +123,8 @@ public final class Marker {
|
|||||||
this.icon = icon;
|
this.icon = icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull
|
@NonNull
|
||||||
Track.Id getTrackId() {
|
public Track.Id getTrackId() {
|
||||||
return trackId;
|
return trackId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,8 +144,8 @@ public final class Marker {
|
|||||||
this.duration = duration;
|
this.duration = duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull
|
@NonNull
|
||||||
Location getLocation() {
|
public Location getLocation() {
|
||||||
return location;
|
return location;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,11 +178,6 @@ public final class Marker {
|
|||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated //TODO Use a Id of null instead
|
|
||||||
public boolean isValid() {
|
|
||||||
return id != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import java.util.Objects;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A track.
|
* A track.
|
||||||
@@ -53,8 +52,8 @@ public class Track {
|
|||||||
/**
|
/**
|
||||||
* May be null if the track was not loaded from the database.
|
* May be null if the track was not loaded from the database.
|
||||||
*/
|
*/
|
||||||
public @Nullable
|
@Nullable
|
||||||
Id getId() {
|
public Id getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,11 +126,6 @@ public class Track {
|
|||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated //TODO Use a Track.Id of null instead
|
|
||||||
public boolean isValid() {
|
|
||||||
return id != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
|||||||
@@ -17,22 +17,30 @@ package de.dennisguse.opentracks.content.data;
|
|||||||
|
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
import android.location.LocationManager;
|
import android.location.LocationManager;
|
||||||
|
import android.os.Parcel;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class extends the standard Android location with extra information.
|
* This class extends the standard Android location with extra information.
|
||||||
*
|
*
|
||||||
* @author Sandor Dornbush
|
* @author Sandor Dornbush
|
||||||
*/
|
*/
|
||||||
|
//TODO Merge constructors by use case; we have too many.
|
||||||
public class TrackPoint {
|
public class TrackPoint {
|
||||||
|
|
||||||
|
private TrackPoint.Id id;
|
||||||
|
|
||||||
private final Location location;
|
private final Location location;
|
||||||
|
|
||||||
private Float heartRate_bpm = null;
|
private Float heartRate_bpm = null;
|
||||||
private Float cyclingCadence_rpm = null;
|
private Float cyclingCadence_rpm = null;
|
||||||
private Float power = null;
|
private Float power = null;
|
||||||
private Float elevationGain = null;
|
private Float elevationGain = null;
|
||||||
|
private Float elevationLoss = null;
|
||||||
|
|
||||||
public TrackPoint() {
|
public TrackPoint() {
|
||||||
this.location = new Location("");
|
this.location = new Location("");
|
||||||
@@ -50,6 +58,7 @@ public class TrackPoint {
|
|||||||
this.power = trackPoint.getPower();
|
this.power = trackPoint.getPower();
|
||||||
|
|
||||||
this.elevationGain = trackPoint.getElevationGain();
|
this.elevationGain = trackPoint.getElevationGain();
|
||||||
|
this.elevationLoss = trackPoint.getElevationLoss();
|
||||||
}
|
}
|
||||||
|
|
||||||
public TrackPoint(double latitude, double longitude, Double altitude, long time) {
|
public TrackPoint(double latitude, double longitude, Double altitude, long time) {
|
||||||
@@ -90,8 +99,20 @@ public class TrackPoint {
|
|||||||
return new TrackPoint(resume);
|
return new TrackPoint(resume);
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull
|
/**
|
||||||
Location getLocation() {
|
* May be null if the track was not loaded from the database.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public TrackPoint.Id getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(TrackPoint.Id id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Location getLocation() {
|
||||||
return location;
|
return location;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,6 +128,18 @@ public class TrackPoint {
|
|||||||
this.elevationGain = elevationGain;
|
this.elevationGain = elevationGain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasElevationLoss() {
|
||||||
|
return elevationLoss != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getElevationLoss() {
|
||||||
|
return elevationLoss;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setElevationLoss(Float elevationLoss) {
|
||||||
|
this.elevationLoss = elevationLoss;
|
||||||
|
}
|
||||||
|
|
||||||
public double getLatitude() {
|
public double getLatitude() {
|
||||||
return location.getLatitude();
|
return location.getLatitude();
|
||||||
}
|
}
|
||||||
@@ -237,4 +270,41 @@ public class TrackPoint {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
return "time=" + getTime() + ": lat=" + getLatitude() + " lng=" + getLongitude() + " acc=" + getAccuracy();
|
return "time=" + getTime() + ": lat=" + getLatitude() + " lng=" + getLongitude() + " acc=" + getAccuracy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class Id {
|
||||||
|
|
||||||
|
private final long id;
|
||||||
|
|
||||||
|
public Id(long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Id(Parcel in) {
|
||||||
|
id = in.readLong();
|
||||||
|
}
|
||||||
|
|
||||||
|
//TOOD Limit visibility to TrackRecordingService / ContentProvider
|
||||||
|
public long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
TrackPoint.Id id1 = (TrackPoint.Id) o;
|
||||||
|
return id == id1.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.valueOf(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ public interface TrackPointsColumns extends BaseColumns {
|
|||||||
String SENSOR_CADENCE = "sensor_cadence";
|
String SENSOR_CADENCE = "sensor_cadence";
|
||||||
String SENSOR_POWER = "sensor_power";
|
String SENSOR_POWER = "sensor_power";
|
||||||
String ELEVATION_GAIN = "elevation_gain";
|
String ELEVATION_GAIN = "elevation_gain";
|
||||||
|
String ELEVATION_LOSS = "elevation_loss";
|
||||||
|
|
||||||
String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " ("
|
String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " ("
|
||||||
+ _ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
|
+ _ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||||
@@ -69,6 +70,7 @@ public interface TrackPointsColumns extends BaseColumns {
|
|||||||
+ SENSOR_CADENCE + " FLOAT, "
|
+ SENSOR_CADENCE + " FLOAT, "
|
||||||
+ SENSOR_POWER + " FLOAT, "
|
+ SENSOR_POWER + " FLOAT, "
|
||||||
+ ELEVATION_GAIN + " FLOAT, "
|
+ ELEVATION_GAIN + " FLOAT, "
|
||||||
|
+ ELEVATION_LOSS + " FLOAT, "
|
||||||
+ "FOREIGN KEY (" + TRACKID + ") REFERENCES " + TracksColumns.TABLE_NAME + "(" + TracksColumns._ID + ") ON UPDATE CASCADE ON DELETE CASCADE"
|
+ "FOREIGN KEY (" + TRACKID + ") REFERENCES " + TracksColumns.TABLE_NAME + "(" + TracksColumns._ID + ") ON UPDATE CASCADE ON DELETE CASCADE"
|
||||||
+ ")";
|
+ ")";
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ public interface TracksColumns extends BaseColumns {
|
|||||||
String MINELEVATION = "minelevation"; // minimum elevation
|
String MINELEVATION = "minelevation"; // minimum elevation
|
||||||
String MAXELEVATION = "maxelevation"; // maximum elevation
|
String MAXELEVATION = "maxelevation"; // maximum elevation
|
||||||
String ELEVATIONGAIN = "elevationgain"; // elevation gain
|
String ELEVATIONGAIN = "elevationgain"; // elevation gain
|
||||||
|
String ELEVATIONLOSS = "elevationloss"; // elevation loss
|
||||||
String ICON = "icon"; // track activity type icon
|
String ICON = "icon"; // track activity type icon
|
||||||
|
|
||||||
String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " ("
|
String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " ("
|
||||||
@@ -74,7 +75,8 @@ public interface TracksColumns extends BaseColumns {
|
|||||||
+ MAXELEVATION + " FLOAT, "
|
+ MAXELEVATION + " FLOAT, "
|
||||||
+ ELEVATIONGAIN + " FLOAT, "
|
+ ELEVATIONGAIN + " FLOAT, "
|
||||||
+ ICON + " TEXT, "
|
+ ICON + " TEXT, "
|
||||||
+ UUID + " BLOB)";
|
+ UUID + " BLOB, "
|
||||||
|
+ ELEVATIONLOSS + " FLOAT)";
|
||||||
|
|
||||||
String CREATE_TABLE_INDEX = "CREATE UNIQUE INDEX " + TABLE_NAME + "_" + UUID + "_index ON " + TABLE_NAME + "(" + UUID + ")";
|
String CREATE_TABLE_INDEX = "CREATE UNIQUE INDEX " + TABLE_NAME + "_" + UUID + "_index ON " + TABLE_NAME + "(" + UUID + ")";
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ class CachedTrackPointsIndexes {
|
|||||||
final int sensorCadenceIndex;
|
final int sensorCadenceIndex;
|
||||||
final int sensorPowerIndex;
|
final int sensorPowerIndex;
|
||||||
final int elevationGainIndex;
|
final int elevationGainIndex;
|
||||||
|
final int elevationLossIndex;
|
||||||
|
|
||||||
CachedTrackPointsIndexes(Cursor cursor) {
|
CachedTrackPointsIndexes(Cursor cursor) {
|
||||||
idIndex = cursor.getColumnIndex(TrackPointsColumns._ID);
|
idIndex = cursor.getColumnIndex(TrackPointsColumns._ID);
|
||||||
@@ -34,5 +35,6 @@ class CachedTrackPointsIndexes {
|
|||||||
sensorCadenceIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_CADENCE);
|
sensorCadenceIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_CADENCE);
|
||||||
sensorPowerIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_POWER);
|
sensorPowerIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_POWER);
|
||||||
elevationGainIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ELEVATION_GAIN);
|
elevationGainIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ELEVATION_GAIN);
|
||||||
|
elevationLossIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ELEVATION_LOSS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import android.database.Cursor;
|
|||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@@ -114,6 +113,7 @@ public class ContentProviderUtils {
|
|||||||
int minElevationIndex = cursor.getColumnIndexOrThrow(TracksColumns.MINELEVATION);
|
int minElevationIndex = cursor.getColumnIndexOrThrow(TracksColumns.MINELEVATION);
|
||||||
int maxElevationIndex = cursor.getColumnIndexOrThrow(TracksColumns.MAXELEVATION);
|
int maxElevationIndex = cursor.getColumnIndexOrThrow(TracksColumns.MAXELEVATION);
|
||||||
int elevationGainIndex = cursor.getColumnIndexOrThrow(TracksColumns.ELEVATIONGAIN);
|
int elevationGainIndex = cursor.getColumnIndexOrThrow(TracksColumns.ELEVATIONGAIN);
|
||||||
|
int elevationLossIndex = cursor.getColumnIndexOrThrow(TracksColumns.ELEVATIONLOSS);
|
||||||
int iconIndex = cursor.getColumnIndexOrThrow(TracksColumns.ICON);
|
int iconIndex = cursor.getColumnIndexOrThrow(TracksColumns.ICON);
|
||||||
|
|
||||||
Track track = new Track();
|
Track track = new Track();
|
||||||
@@ -160,6 +160,9 @@ public class ContentProviderUtils {
|
|||||||
if (!cursor.isNull(elevationGainIndex)) {
|
if (!cursor.isNull(elevationGainIndex)) {
|
||||||
trackStatistics.setTotalElevationGain(cursor.getFloat(elevationGainIndex));
|
trackStatistics.setTotalElevationGain(cursor.getFloat(elevationGainIndex));
|
||||||
}
|
}
|
||||||
|
if (!cursor.isNull(elevationLossIndex)) {
|
||||||
|
trackStatistics.setTotalElevationLoss(cursor.getFloat(elevationLossIndex));
|
||||||
|
}
|
||||||
if (!cursor.isNull(iconIndex)) {
|
if (!cursor.isNull(iconIndex)) {
|
||||||
track.setIcon(cursor.getString(iconIndex));
|
track.setIcon(cursor.getString(iconIndex));
|
||||||
}
|
}
|
||||||
@@ -203,7 +206,6 @@ public class ContentProviderUtils {
|
|||||||
contentResolver.delete(MarkerColumns.CONTENT_URI, MarkerColumns.TRACKID + "=?", selectionArgs);
|
contentResolver.delete(MarkerColumns.CONTENT_URI, MarkerColumns.TRACKID + "=?", selectionArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
public List<Track> getTracks() {
|
public List<Track> getTracks() {
|
||||||
ArrayList<Track> tracks = new ArrayList<>();
|
ArrayList<Track> tracks = new ArrayList<>();
|
||||||
try (Cursor cursor = getTrackCursor(null, null, TracksColumns._ID)) {
|
try (Cursor cursor = getTrackCursor(null, null, TracksColumns._ID)) {
|
||||||
@@ -217,24 +219,11 @@ public class ContentProviderUtils {
|
|||||||
return tracks;
|
return tracks;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
//TODO Do not use; rather get the proper TrackId (e.g., resume a track might give you some weird effects).
|
|
||||||
@VisibleForTesting
|
|
||||||
public Track getLastTrack() {
|
|
||||||
try (Cursor cursor = getTrackCursor(null, null, TracksColumns.STARTTIME + " DESC")) {
|
|
||||||
// Using the same order as shown in the track list
|
|
||||||
if (cursor != null && cursor.moveToNext()) {
|
|
||||||
return createTrack(cursor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param trackId the track id.
|
* @param trackId the track id.
|
||||||
*/
|
*/
|
||||||
public Track getTrack(Track.Id trackId) {
|
public Track getTrack(Track.Id trackId) {
|
||||||
if (trackId == null || !trackId.isValid()) {
|
if (trackId == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try (Cursor cursor = getTrackCursor(TracksColumns._ID + "=?", new String[]{Long.toString(trackId.getId())}, null)) {
|
try (Cursor cursor = getTrackCursor(TracksColumns._ID + "=?", new String[]{Long.toString(trackId.getId())}, null)) {
|
||||||
@@ -296,7 +285,7 @@ public class ContentProviderUtils {
|
|||||||
TrackStatistics trackStatistics = track.getTrackStatistics();
|
TrackStatistics trackStatistics = track.getTrackStatistics();
|
||||||
|
|
||||||
// Value < 0 indicates no id is available
|
// Value < 0 indicates no id is available
|
||||||
if (track.getId() != null && track.getId().isValid()) {
|
if (track.getId() != null) {
|
||||||
values.put(TracksColumns._ID, track.getId().getId());
|
values.put(TracksColumns._ID, track.getId().getId());
|
||||||
}
|
}
|
||||||
values.put(TracksColumns.UUID, UUIDUtils.toBytes(track.getUuid()));
|
values.put(TracksColumns.UUID, UUIDUtils.toBytes(track.getUuid()));
|
||||||
@@ -314,6 +303,7 @@ public class ContentProviderUtils {
|
|||||||
values.put(TracksColumns.MINELEVATION, trackStatistics.getMinElevation());
|
values.put(TracksColumns.MINELEVATION, trackStatistics.getMinElevation());
|
||||||
values.put(TracksColumns.MAXELEVATION, trackStatistics.getMaxElevation());
|
values.put(TracksColumns.MAXELEVATION, trackStatistics.getMaxElevation());
|
||||||
values.put(TracksColumns.ELEVATIONGAIN, trackStatistics.getTotalElevationGain());
|
values.put(TracksColumns.ELEVATIONGAIN, trackStatistics.getTotalElevationGain());
|
||||||
|
values.put(TracksColumns.ELEVATIONLOSS, trackStatistics.getTotalElevationLoss());
|
||||||
values.put(TracksColumns.ICON, track.getIcon());
|
values.put(TracksColumns.ICON, track.getIcon());
|
||||||
|
|
||||||
return values;
|
return values;
|
||||||
@@ -394,10 +384,7 @@ public class ContentProviderUtils {
|
|||||||
/**
|
/**
|
||||||
* @return -1 if not able to get the next marker number.
|
* @return -1 if not able to get the next marker number.
|
||||||
*/
|
*/
|
||||||
public int getNextMarkerNumber(Track.Id trackId) {
|
public int getNextMarkerNumber(@NonNull Track.Id trackId) {
|
||||||
if (!trackId.isValid()) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
String[] projection = {MarkerColumns._ID};
|
String[] projection = {MarkerColumns._ID};
|
||||||
String selection = MarkerColumns.TRACKID + "=?";
|
String selection = MarkerColumns.TRACKID + "=?";
|
||||||
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
|
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
|
||||||
@@ -409,10 +396,7 @@ public class ContentProviderUtils {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Marker getMarker(Marker.Id markerId) {
|
public Marker getMarker(@NonNull Marker.Id markerId) {
|
||||||
if (!markerId.isValid()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
try (Cursor cursor = getMarkerCursor(null, MarkerColumns._ID + "=?", new String[]{Long.toString(markerId.getId())}, MarkerColumns._ID, 1)) {
|
try (Cursor cursor = getMarkerCursor(null, MarkerColumns._ID + "=?", new String[]{Long.toString(markerId.getId())}, MarkerColumns._ID, 1)) {
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
return createMarker(cursor);
|
return createMarker(cursor);
|
||||||
@@ -428,14 +412,10 @@ public class ContentProviderUtils {
|
|||||||
* @param minMarkerId the minimum marker id. null to ignore
|
* @param minMarkerId the minimum marker id. null to ignore
|
||||||
* @param maxCount the maximum number of markers to return. -1 for no limit
|
* @param maxCount the maximum number of markers to return. -1 for no limit
|
||||||
*/
|
*/
|
||||||
public Cursor getMarkerCursor(Track.Id trackId, @Nullable Marker.Id minMarkerId, int maxCount) {
|
public Cursor getMarkerCursor(@NonNull Track.Id trackId, @Nullable Marker.Id minMarkerId, int maxCount) {
|
||||||
if (!trackId.isValid()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
String selection;
|
String selection;
|
||||||
String[] selectionArgs;
|
String[] selectionArgs;
|
||||||
if (minMarkerId != null && minMarkerId.isValid()) {
|
if (minMarkerId != null) {
|
||||||
selection = MarkerColumns.TRACKID + "=? AND " + MarkerColumns._ID + ">=?";
|
selection = MarkerColumns.TRACKID + "=? AND " + MarkerColumns._ID + ">=?";
|
||||||
selectionArgs = new String[]{Long.toString(trackId.getId()), Long.toString(minMarkerId.getId())};
|
selectionArgs = new String[]{Long.toString(trackId.getId()), Long.toString(minMarkerId.getId())};
|
||||||
} else {
|
} else {
|
||||||
@@ -461,10 +441,6 @@ public class ContentProviderUtils {
|
|||||||
|
|
||||||
@Deprecated //TODO TracksColumns.MARKER_COUNT while querying for tracks
|
@Deprecated //TODO TracksColumns.MARKER_COUNT while querying for tracks
|
||||||
public int getMarkerCount(Track.Id trackId) {
|
public int getMarkerCount(Track.Id trackId) {
|
||||||
if (!trackId.isValid()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
String[] projection = new String[]{"count(*) AS count"};
|
String[] projection = new String[]{"count(*) AS count"};
|
||||||
String selection = MarkerColumns.TRACKID + "=?";
|
String selection = MarkerColumns.TRACKID + "=?";
|
||||||
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
|
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
|
||||||
@@ -488,7 +464,7 @@ public class ContentProviderUtils {
|
|||||||
private void deleteMarkerPhoto(Context context, Marker marker) {
|
private void deleteMarkerPhoto(Context context, Marker marker) {
|
||||||
if (marker != null && marker.hasPhoto()) {
|
if (marker != null && marker.hasPhoto()) {
|
||||||
Uri uri = marker.getPhotoURI();
|
Uri uri = marker.getPhotoURI();
|
||||||
File file = FileUtils.getPhotoFileIfExists(context, marker.getTrackId(), uri);
|
File file = FileUtils.buildInternalPhotoFile(context, marker.getTrackId(), uri);
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
File parent = file.getParentFile();
|
File parent = file.getParentFile();
|
||||||
file.delete();
|
file.delete();
|
||||||
@@ -515,7 +491,7 @@ public class ContentProviderUtils {
|
|||||||
ContentValues createContentValues(@NonNull Marker marker) {
|
ContentValues createContentValues(@NonNull Marker marker) {
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
|
|
||||||
if (marker.getId() != null && marker.getId().isValid()) {
|
if (marker.getId() != null) {
|
||||||
values.put(MarkerColumns._ID, marker.getId().getId());
|
values.put(MarkerColumns._ID, marker.getId().getId());
|
||||||
}
|
}
|
||||||
values.put(MarkerColumns.NAME, marker.getName());
|
values.put(MarkerColumns.NAME, marker.getName());
|
||||||
@@ -569,6 +545,9 @@ public class ContentProviderUtils {
|
|||||||
*/
|
*/
|
||||||
static TrackPoint fillTrackPoint(Cursor cursor, CachedTrackPointsIndexes indexes) {
|
static TrackPoint fillTrackPoint(Cursor cursor, CachedTrackPointsIndexes indexes) {
|
||||||
TrackPoint trackPoint = new TrackPoint();
|
TrackPoint trackPoint = new TrackPoint();
|
||||||
|
if (!cursor.isNull(indexes.longitudeIndex)) {
|
||||||
|
trackPoint.setId(new TrackPoint.Id(cursor.getInt(indexes.idIndex)));
|
||||||
|
}
|
||||||
|
|
||||||
if (!cursor.isNull(indexes.longitudeIndex)) {
|
if (!cursor.isNull(indexes.longitudeIndex)) {
|
||||||
trackPoint.setLongitude(((double) cursor.getInt(indexes.longitudeIndex)) / 1E6);
|
trackPoint.setLongitude(((double) cursor.getInt(indexes.longitudeIndex)) / 1E6);
|
||||||
@@ -605,22 +584,18 @@ public class ContentProviderUtils {
|
|||||||
if (!cursor.isNull(indexes.elevationGainIndex)) {
|
if (!cursor.isNull(indexes.elevationGainIndex)) {
|
||||||
trackPoint.setElevationGain(cursor.getFloat(indexes.elevationGainIndex));
|
trackPoint.setElevationGain(cursor.getFloat(indexes.elevationGainIndex));
|
||||||
}
|
}
|
||||||
|
if (!cursor.isNull(indexes.elevationLossIndex)) {
|
||||||
|
trackPoint.setElevationLoss(cursor.getFloat(indexes.elevationLossIndex));
|
||||||
|
}
|
||||||
|
|
||||||
return trackPoint;
|
return trackPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
//TODO Only used for file import; might be better to replace it.
|
||||||
* Inserts multiple trackPoints.
|
public int bulkInsertTrackPoint(List<TrackPoint> trackPoints, Track.Id trackId) {
|
||||||
*
|
ContentValues[] values = new ContentValues[trackPoints.size()];
|
||||||
* @param trackPoints an array of trackPoints
|
for (int i = 0; i < trackPoints.size(); i++) {
|
||||||
* @param trackId the trackPoints id
|
values[i] = createContentValues(trackPoints.get(i), trackId);
|
||||||
* @return the number of trackPoints inserted
|
|
||||||
*/
|
|
||||||
//TODO Only used for testing and file import; might be better to replace it.
|
|
||||||
public int bulkInsertTrackPoint(TrackPoint[] trackPoints, Track.Id trackId) {
|
|
||||||
ContentValues[] values = new ContentValues[trackPoints.length];
|
|
||||||
for (int i = 0; i < values.length; i++) {
|
|
||||||
values[i] = createContentValues(trackPoints[i], trackId);
|
|
||||||
}
|
}
|
||||||
return contentResolver.bulkInsert(TrackPointsColumns.CONTENT_URI_BY_ID, values);
|
return contentResolver.bulkInsert(TrackPointsColumns.CONTENT_URI_BY_ID, values);
|
||||||
}
|
}
|
||||||
@@ -633,9 +608,6 @@ public class ContentProviderUtils {
|
|||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public Track.Id getFirstTrackPointId(Track.Id trackId) {
|
public Track.Id getFirstTrackPointId(Track.Id trackId) {
|
||||||
if (!trackId.isValid()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String selection = TrackPointsColumns._ID + "=(SELECT MIN(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=?)";
|
String selection = TrackPointsColumns._ID + "=(SELECT MIN(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=?)";
|
||||||
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
|
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
|
||||||
try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) {
|
try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) {
|
||||||
@@ -653,19 +625,15 @@ public class ContentProviderUtils {
|
|||||||
* @param trackId the track id
|
* @param trackId the track id
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public long getLastTrackPointId(Track.Id trackId) {
|
public TrackPoint.Id getLastTrackPointId(@NonNull Track.Id trackId) {
|
||||||
if (trackId == null || !trackId.isValid()) {
|
|
||||||
Log.w(TAG, "Fix callers who do this.");
|
|
||||||
return -1L;
|
|
||||||
}
|
|
||||||
String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") from " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=?)";
|
String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") from " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=?)";
|
||||||
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
|
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
|
||||||
try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) {
|
try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) {
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
return cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID));
|
return new TrackPoint.Id(cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1L;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -675,18 +643,15 @@ public class ContentProviderUtils {
|
|||||||
* @param location the location
|
* @param location the location
|
||||||
* @return trackPoint id if the location is in the track. -1L otherwise.
|
* @return trackPoint id if the location is in the track. -1L otherwise.
|
||||||
*/
|
*/
|
||||||
public long getTrackPointId(Track.Id trackId, Location location) {
|
public TrackPoint.Id getTrackPointId(Track.Id trackId, Location location) {
|
||||||
if (!trackId.isValid()) {
|
|
||||||
return -1L;
|
|
||||||
}
|
|
||||||
String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns.TIME + "=?)";
|
String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns.TIME + "=?)";
|
||||||
String[] selectionArgs = new String[]{Long.toString(trackId.getId()), Long.toString(location.getTime())};
|
String[] selectionArgs = new String[]{Long.toString(trackId.getId()), Long.toString(location.getTime())};
|
||||||
try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) {
|
try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) {
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
return cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID));
|
return new TrackPoint.Id(cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1L;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -702,31 +667,22 @@ public class ContentProviderUtils {
|
|||||||
* Creates a location cursor. The caller owns the returned cursor and is responsible for closing it.
|
* Creates a location cursor. The caller owns the returned cursor and is responsible for closing it.
|
||||||
*
|
*
|
||||||
* @param trackId the track id
|
* @param trackId the track id
|
||||||
* @param startTrackPointId the starting trackPoint id. -1L to ignore
|
* @param startTrackPointId the starting trackPoint id. `null` to ignore
|
||||||
* @param maxLocations maximum number of locations to return. -1 for no limit
|
* @param maxLocations maximum number of locations to return. `null` for no limit
|
||||||
* @param descending true to sort the result in descending order (latest location first)
|
|
||||||
*/
|
*/
|
||||||
public Cursor getTrackPointCursor(Track.Id trackId, long startTrackPointId, int maxLocations, boolean descending) {
|
public Cursor getTrackPointCursor(Track.Id trackId, TrackPoint.Id startTrackPointId, Integer maxLocations) {
|
||||||
if (!trackId.isValid()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
String selection;
|
String selection;
|
||||||
String[] selectionArgs;
|
String[] selectionArgs;
|
||||||
if (startTrackPointId >= 0) {
|
if (startTrackPointId != null) {
|
||||||
String comparison = descending ? "<=" : ">=";
|
selection = TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns._ID + ">=?";
|
||||||
selection = TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns._ID + comparison + "?";
|
selectionArgs = new String[]{Long.toString(trackId.getId()), Long.toString(startTrackPointId.getId())};
|
||||||
selectionArgs = new String[]{Long.toString(trackId.getId()), Long.toString(startTrackPointId)};
|
|
||||||
} else {
|
} else {
|
||||||
selection = TrackPointsColumns.TRACKID + "=?";
|
selection = TrackPointsColumns.TRACKID + "=?";
|
||||||
selectionArgs = new String[]{Long.toString(trackId.getId())};
|
selectionArgs = new String[]{Long.toString(trackId.getId())};
|
||||||
}
|
}
|
||||||
|
|
||||||
String sortOrder = TrackPointsColumns._ID;
|
String sortOrder = TrackPointsColumns.DEFAULT_SORT_ORDER;
|
||||||
if (descending) {
|
if (maxLocations != null) {
|
||||||
sortOrder += " DESC";
|
|
||||||
}
|
|
||||||
if (maxLocations >= 0) {
|
|
||||||
sortOrder += " LIMIT " + maxLocations;
|
sortOrder += " LIMIT " + maxLocations;
|
||||||
}
|
}
|
||||||
return getTrackPointCursor(null, selection, selectionArgs, sortOrder);
|
return getTrackPointCursor(null, selection, selectionArgs, sortOrder);
|
||||||
@@ -740,9 +696,6 @@ public class ContentProviderUtils {
|
|||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public TrackPoint getLastValidTrackPoint(Track.Id trackId) {
|
public TrackPoint getLastValidTrackPoint(Track.Id trackId) {
|
||||||
if (!trackId.isValid()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns.LATITUDE + "<=" + MAX_LATITUDE + ")";
|
String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns.LATITUDE + "<=" + MAX_LATITUDE + ")";
|
||||||
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
|
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
|
||||||
return findTrackPointBy(selection, selectionArgs);
|
return findTrackPointBy(selection, selectionArgs);
|
||||||
@@ -798,6 +751,9 @@ public class ContentProviderUtils {
|
|||||||
if (trackPoint.hasElevationGain()) {
|
if (trackPoint.hasElevationGain()) {
|
||||||
values.put(TrackPointsColumns.ELEVATION_GAIN, trackPoint.getElevationGain());
|
values.put(TrackPointsColumns.ELEVATION_GAIN, trackPoint.getElevationGain());
|
||||||
}
|
}
|
||||||
|
if (trackPoint.hasElevationLoss()) {
|
||||||
|
values.put(TrackPointsColumns.ELEVATION_LOSS, trackPoint.getElevationLoss());
|
||||||
|
}
|
||||||
|
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
@@ -810,11 +766,10 @@ public class ContentProviderUtils {
|
|||||||
* When done with iteration, {@link TrackPointIterator#close()} must be called.
|
* When done with iteration, {@link TrackPointIterator#close()} must be called.
|
||||||
*
|
*
|
||||||
* @param trackId the track id
|
* @param trackId the track id
|
||||||
* @param startTrackPointId the starting trackPoint id. -1L to ignore
|
* @param startTrackPointId the starting trackPoint id. `null` to ignore
|
||||||
* @param descending true to sort the result in descending order (latest location first)
|
|
||||||
*/
|
*/
|
||||||
public TrackPointIterator getTrackPointLocationIterator(final Track.Id trackId, final long startTrackPointId, final boolean descending) {
|
public TrackPointIterator getTrackPointLocationIterator(final Track.Id trackId, final TrackPoint.Id startTrackPointId) {
|
||||||
return new TrackPointIterator(this, trackId, startTrackPointId, descending);
|
return new TrackPointIterator(this, trackId, startTrackPointId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TrackPoint findTrackPointBy(String selection, String[] selectionArgs) {
|
private TrackPoint findTrackPointBy(String selection, String[] selectionArgs) {
|
||||||
@@ -842,7 +797,7 @@ public class ContentProviderUtils {
|
|||||||
public List<TrackPoint> getTrackPoints(Track.Id trackId) {
|
public List<TrackPoint> getTrackPoints(Track.Id trackId) {
|
||||||
List<TrackPoint> trackPoints = null;
|
List<TrackPoint> trackPoints = null;
|
||||||
|
|
||||||
try (Cursor trackPointCursor = getTrackPointCursor(trackId, -1L, -1, false)) {
|
try (Cursor trackPointCursor = getTrackPointCursor(trackId, null, null)) {
|
||||||
if (trackPointCursor != null) {
|
if (trackPointCursor != null) {
|
||||||
trackPointCursor.moveToFirst();
|
trackPointCursor.moveToFirst();
|
||||||
trackPoints = new ArrayList<>(trackPointCursor.getCount());
|
trackPoints = new ArrayList<>(trackPointCursor.getCount());
|
||||||
|
|||||||
+111
-1
@@ -25,7 +25,7 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
|||||||
|
|
||||||
private static final String TAG = CustomSQLiteOpenHelper.class.getSimpleName();
|
private static final String TAG = CustomSQLiteOpenHelper.class.getSimpleName();
|
||||||
|
|
||||||
private static final int DATABASE_VERSION = 28;
|
private static final int DATABASE_VERSION = 29;
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public static final String DATABASE_NAME = "database.db";
|
public static final String DATABASE_NAME = "database.db";
|
||||||
@@ -76,6 +76,12 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
|||||||
case 28:
|
case 28:
|
||||||
upgradeFrom27to28(db);
|
upgradeFrom27to28(db);
|
||||||
break;
|
break;
|
||||||
|
case 29:
|
||||||
|
upgradeFrom28to29(db);
|
||||||
|
break;
|
||||||
|
case 30:
|
||||||
|
upgradeFrom29to30(db);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException("Not implemented: upgrade to " + toVersion);
|
throw new RuntimeException("Not implemented: upgrade to " + toVersion);
|
||||||
@@ -103,6 +109,12 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
|||||||
case 27:
|
case 27:
|
||||||
downgradeFrom28to27(db);
|
downgradeFrom28to27(db);
|
||||||
break;
|
break;
|
||||||
|
case 28:
|
||||||
|
downgradeFrom29to28(db);
|
||||||
|
break;
|
||||||
|
case 29:
|
||||||
|
downgradeFrom30to29(db);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException("Not implemented: downgrade to " + toVersion);
|
throw new RuntimeException("Not implemented: downgrade to " + toVersion);
|
||||||
@@ -284,4 +296,102 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
|||||||
db.setTransactionSuccessful();
|
db.setTransactionSuccessful();
|
||||||
db.endTransaction();
|
db.endTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add elevation loss.
|
||||||
|
*/
|
||||||
|
private void upgradeFrom28to29(SQLiteDatabase db) {
|
||||||
|
db.beginTransaction();
|
||||||
|
|
||||||
|
db.execSQL("ALTER TABLE tracks ADD COLUMN elevationloss FLOAT");
|
||||||
|
db.execSQL("ALTER TABLE trackpoints ADD COLUMN elevation_loss FLOAT");
|
||||||
|
|
||||||
|
db.setTransactionSuccessful();
|
||||||
|
db.endTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void downgradeFrom29to28(SQLiteDatabase db) {
|
||||||
|
db.beginTransaction();
|
||||||
|
|
||||||
|
// Tracks
|
||||||
|
db.execSQL("DROP INDEX tracks_uuid_index");
|
||||||
|
|
||||||
|
db.execSQL("ALTER TABLE tracks RENAME TO tracks_old");
|
||||||
|
db.execSQL("CREATE TABLE tracks (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, starttime INTEGER, stoptime INTEGER, numpoints INTEGER, totaldistance FLOAT, totaltime INTEGER, movingtime INTEGER, avgspeed FLOAT, avgmovingspeed FLOAT, maxspeed FLOAT, minelevation FLOAT, maxelevation FLOAT, elevationgain FLOAT, icon TEXT, uuid BLOB)");
|
||||||
|
db.execSQL("INSERT INTO tracks SELECT _id, name, description, category, starttime, stoptime, numpoints, totaldistance, totaltime, movingtime, avgspeed, avgmovingspeed, maxspeed, minelevation, maxelevation, elevationgain, icon, uuid FROM tracks_old");
|
||||||
|
db.execSQL("DROP TABLE tracks_old");
|
||||||
|
|
||||||
|
db.execSQL("CREATE UNIQUE INDEX tracks_uuid_index ON tracks(uuid)");
|
||||||
|
|
||||||
|
// TrackPoints
|
||||||
|
db.execSQL("DROP INDEX trackpoints_trackid_index");
|
||||||
|
|
||||||
|
db.execSQL("ALTER TABLE trackpoints RENAME TO trackpoints_old");
|
||||||
|
db.execSQL("CREATE TABLE trackpoints (_id INTEGER PRIMARY KEY AUTOINCREMENT, trackid INTEGER NOT NULL, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, speed FLOAT, bearing FLOAT, sensor_heartrate FLOAT, sensor_cadence FLOAT, sensor_power FLOAT, elevation_gain FLOAT, FOREIGN KEY (trackid) REFERENCES tracks(_id) ON UPDATE CASCADE ON DELETE CASCADE)");
|
||||||
|
db.execSQL("INSERT INTO trackpoints SELECT _id, trackid, longitude, latitude, time, elevation, accuracy, speed, bearing, sensor_heartrate, sensor_cadence, sensor_power, elevation_gain FROM trackpoints_old");
|
||||||
|
db.execSQL("DROP TABLE trackpoints_old");
|
||||||
|
|
||||||
|
db.execSQL("CREATE INDEX trackpoints_trackid_index ON trackpoints(trackid)");
|
||||||
|
|
||||||
|
db.setTransactionSuccessful();
|
||||||
|
db.endTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move TrackPoint type (segment start vs. segment end) into separate column.
|
||||||
|
*/
|
||||||
|
private void upgradeFrom29to30(SQLiteDatabase db) {
|
||||||
|
db.beginTransaction();
|
||||||
|
|
||||||
|
// TrackPoints
|
||||||
|
db.execSQL("ALTER TABLE trackpoints ADD COLUMN type TEXT CHECK(type IN (-2, -1, 0, 1))");
|
||||||
|
db.execSQL("UPDATE trackpoints SET type = -2, latitude = NULL, longitude = NULL WHERE latitude = 200 * 1E6");
|
||||||
|
db.execSQL("UPDATE trackpoints SET type = 1, latitude = NULL, longitude = NULL WHERE latitude = 100 * 1E6");
|
||||||
|
db.execSQL("UPDATE trackpoints SET type = 0 WHERE type IS NULL");
|
||||||
|
|
||||||
|
// PAUSE markers without RESUME were inserted automatically as segment markers if the distance between subsequent trackpoints was too great.
|
||||||
|
// Only stored data is time (local device time); not meaningful as trackpoints were stored with GPS time.
|
||||||
|
// 1. Mark there successors as SEGMENT_START_AUTOMATIC
|
||||||
|
db.execSQL(
|
||||||
|
"UPDATE trackpoints " +
|
||||||
|
"SET type = -1 " +
|
||||||
|
"WHERE type = 0 AND 1 = " +
|
||||||
|
"(SELECT type FROM trackpoints AS T1 " +
|
||||||
|
"WHERE trackpoints._id > t1._id " +
|
||||||
|
"AND trackpoints.trackid = t1.trackid " +
|
||||||
|
"ORDER BY t1._id DESC " +
|
||||||
|
"LIMIT 1)");
|
||||||
|
// 2. Delete old PAUSE trackpoint
|
||||||
|
db.execSQL(
|
||||||
|
"DELETE FROM trackpoints" +
|
||||||
|
" WHERE type = 1 AND -1 = " +
|
||||||
|
"(SELECT type FROM trackpoints AS T1 " +
|
||||||
|
"WHERE trackpoints._id < t1._id " +
|
||||||
|
"AND trackpoints.trackid = t1.trackid " +
|
||||||
|
"ORDER BY t1._id ASC " +
|
||||||
|
"LIMIT 1)");
|
||||||
|
|
||||||
|
db.setTransactionSuccessful();
|
||||||
|
db.endTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void downgradeFrom30to29(SQLiteDatabase db) {
|
||||||
|
db.beginTransaction();
|
||||||
|
|
||||||
|
// TrackPoints
|
||||||
|
//TODO That does not restore deleted trackpoints
|
||||||
|
db.execSQL("UPDATE trackpoints SET latitude = 200 * 1E6, longitude = NULL WHERE type = -2");
|
||||||
|
db.execSQL("UPDATE trackpoints SET latitude = 100 * 1E6, longitude = NULL WHERE type = 1");
|
||||||
|
|
||||||
|
// TrackPoints; identical to upgradeFrom27to28()
|
||||||
|
db.execSQL("ALTER TABLE trackpoints RENAME TO trackpoints_old");
|
||||||
|
db.execSQL("CREATE TABLE trackpoints (_id INTEGER PRIMARY KEY AUTOINCREMENT, trackid INTEGER NOT NULL, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, speed FLOAT, bearing FLOAT, sensor_heartrate FLOAT, sensor_cadence FLOAT, sensor_power FLOAT, elevation_gain FLOAT, elevation_loss FLOAT, FOREIGN KEY (trackid) REFERENCES tracks(_id) ON UPDATE CASCADE ON DELETE CASCADE)");
|
||||||
|
db.execSQL("INSERT INTO trackpoints SELECT _id, trackid, longitude, latitude, time, elevation, accuracy, speed, bearing, sensor_heartrate, sensor_cadence, sensor_power, elevation_gain, elevation_gain FROM trackpoints_old");
|
||||||
|
db.execSQL("DROP TABLE trackpoints_old");
|
||||||
|
|
||||||
|
db.execSQL("CREATE INDEX trackpoints_trackid_index ON trackpoints(trackid)");
|
||||||
|
|
||||||
|
db.setTransactionSuccessful();
|
||||||
|
db.endTransaction();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,16 +18,14 @@ public class TrackPointIterator implements Iterator<TrackPoint>, AutoCloseable {
|
|||||||
|
|
||||||
private final ContentProviderUtils contentProviderUtils;
|
private final ContentProviderUtils contentProviderUtils;
|
||||||
private final Track.Id trackId;
|
private final Track.Id trackId;
|
||||||
private final boolean descending;
|
|
||||||
private final CachedTrackPointsIndexes indexes;
|
private final CachedTrackPointsIndexes indexes;
|
||||||
private long lastTrackPointId = -1L;
|
private TrackPoint.Id lastTrackPointId = null;
|
||||||
private Cursor cursor;
|
private Cursor cursor;
|
||||||
|
|
||||||
|
|
||||||
public TrackPointIterator(ContentProviderUtils contentProviderUtils, Track.Id trackId, long startTrackPointId, boolean descending) {
|
public TrackPointIterator(ContentProviderUtils contentProviderUtils, Track.Id trackId, TrackPoint.Id startTrackPointId) {
|
||||||
this.contentProviderUtils = contentProviderUtils;
|
this.contentProviderUtils = contentProviderUtils;
|
||||||
this.trackId = trackId;
|
this.trackId = trackId;
|
||||||
this.descending = descending;
|
|
||||||
|
|
||||||
cursor = getCursor(startTrackPointId);
|
cursor = getCursor(startTrackPointId);
|
||||||
indexes = cursor != null ? new CachedTrackPointsIndexes(cursor)
|
indexes = cursor != null ? new CachedTrackPointsIndexes(cursor)
|
||||||
@@ -39,25 +37,21 @@ public class TrackPointIterator implements Iterator<TrackPoint>, AutoCloseable {
|
|||||||
*
|
*
|
||||||
* @param trackPointId the starting track point id
|
* @param trackPointId the starting track point id
|
||||||
*/
|
*/
|
||||||
private Cursor getCursor(long trackPointId) {
|
private Cursor getCursor(TrackPoint.Id trackPointId) {
|
||||||
return contentProviderUtils.getTrackPointCursor(trackId, trackPointId, contentProviderUtils.getDefaultCursorBatchSize(), descending);
|
return contentProviderUtils.getTrackPointCursor(trackId, trackPointId, contentProviderUtils.getDefaultCursorBatchSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Advances the cursor to the next batch. Returns true if successful.
|
* Advances the cursor to the next batch. Returns true if successful.
|
||||||
*/
|
*/
|
||||||
private boolean advanceCursorToNextBatch() {
|
private boolean advanceCursorToNextBatch() {
|
||||||
long trackPointId = lastTrackPointId == -1L ? -1L : lastTrackPointId + (descending ? -1 : 1);
|
TrackPoint.Id trackPointId = lastTrackPointId == null ? null : new TrackPoint.Id(lastTrackPointId.getId() + 1);
|
||||||
Log.d(TAG, "Advancing track point id: " + trackPointId);
|
Log.d(TAG, "Advancing track point id: " + trackPointId);
|
||||||
cursor.close();
|
cursor.close();
|
||||||
cursor = getCursor(trackPointId);
|
cursor = getCursor(trackPointId);
|
||||||
return cursor != null;
|
return cursor != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getTrackPointId() {
|
|
||||||
return lastTrackPointId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasNext() {
|
public boolean hasNext() {
|
||||||
if (cursor == null) {
|
if (cursor == null) {
|
||||||
@@ -85,7 +79,7 @@ public class TrackPointIterator implements Iterator<TrackPoint>, AutoCloseable {
|
|||||||
throw new NoSuchElementException();
|
throw new NoSuchElementException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lastTrackPointId = cursor.getLong(indexes.idIndex);
|
lastTrackPointId = new TrackPoint.Id(cursor.getLong(indexes.idIndex));
|
||||||
return ContentProviderUtils.fillTrackPoint(cursor, indexes);
|
return ContentProviderUtils.fillTrackPoint(cursor, indexes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -58,7 +58,7 @@ public class ChooseActivityTypeDialogFragment extends DialogFragment implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
viewBinding = ChooseActivityTypeBinding.inflate(inflater, container, false);
|
viewBinding = ChooseActivityTypeBinding.inflate(inflater, container, false);
|
||||||
|
|
||||||
List<Integer> imageIds = new ArrayList<>();
|
List<Integer> imageIds = new ArrayList<>();
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
|
|||||||
protected IntervalStatisticsAdapter.StackMode stackModeListView;
|
protected IntervalStatisticsAdapter.StackMode stackModeListView;
|
||||||
private IntervalStatisticsModel.IntervalOption selectedInterval;
|
private IntervalStatisticsModel.IntervalOption selectedInterval;
|
||||||
|
|
||||||
private String intervalUnit;
|
private boolean metricUnits;
|
||||||
private IntervalStatisticsAdapter adapter;
|
private IntervalStatisticsAdapter adapter;
|
||||||
private ArrayAdapter<IntervalStatisticsModel.IntervalOption> spinnerAdapter;
|
private ArrayAdapter<IntervalStatisticsModel.IntervalOption> spinnerAdapter;
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
|
|||||||
|
|
||||||
protected final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (preferences, key) -> {
|
protected final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (preferences, key) -> {
|
||||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key) || PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
|
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key) || PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
|
||||||
intervalUnit = PreferencesUtils.isMetricUnits(getContext()) ? getContext().getString(R.string.unit_kilometer) : getContext().getString(R.string.unit_mile);
|
metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
||||||
if (adapter != null) {
|
if (adapter != null) {
|
||||||
adapter.notifyDataSetChanged();
|
adapter.notifyDataSetChanged();
|
||||||
spinnerAdapter.notifyDataSetChanged();
|
spinnerAdapter.notifyDataSetChanged();
|
||||||
@@ -74,7 +74,7 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
|
|||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
PreferencesUtils.register(getContext(), sharedPreferenceChangeListener);
|
PreferencesUtils.register(getContext(), sharedPreferenceChangeListener);
|
||||||
intervalUnit = PreferencesUtils.isMetricUnits(getContext()) ? getContext().getString(R.string.unit_kilometer) : getContext().getString(R.string.unit_mile);
|
metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
||||||
|
|
||||||
viewBinding.intervalList.setEmptyView(viewBinding.intervalListEmptyView);
|
viewBinding.intervalList.setEmptyView(viewBinding.intervalListEmptyView);
|
||||||
|
|
||||||
@@ -87,15 +87,17 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
|
|||||||
@Override
|
@Override
|
||||||
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||||
TextView v = (TextView) super.getView(position, convertView, parent);
|
TextView v = (TextView) super.getView(position, convertView, parent);
|
||||||
v.setText(v.getText() + " " + intervalUnit);
|
if (metricUnits) {
|
||||||
|
v.setText(getContext().getString(R.string.value_integer_kilometer, Integer.parseInt(v.getText().toString())));
|
||||||
|
} else {
|
||||||
|
v.setText(getContext().getString(R.string.value_integer_mile, Integer.parseInt(v.getText().toString())));
|
||||||
|
}
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||||
TextView v = (TextView) super.getDropDownView(position, convertView, parent);
|
return getView(position, convertView, parent);
|
||||||
v.setText(v.getText() + " " + intervalUnit);
|
|
||||||
return v;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -149,7 +151,6 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
|
||||||
IntervalStatistics intervalStatistics = viewModel.getIntervalStats(metricUnits, selectedInterval);
|
IntervalStatistics intervalStatistics = viewModel.getIntervalStats(metricUnits, selectedInterval);
|
||||||
adapter = new IntervalStatisticsAdapter(getContext(), intervalStatistics.getIntervalList(), category, stackModeListView);
|
adapter = new IntervalStatisticsAdapter(getContext(), intervalStatistics.getIntervalList(), category, stackModeListView);
|
||||||
viewBinding.intervalList.setAdapter(adapter);
|
viewBinding.intervalList.setAdapter(adapter);
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ public class MarkerDetailFragment extends Fragment {
|
|||||||
|
|
||||||
private ContentProviderUtils contentProviderUtils;
|
private ContentProviderUtils contentProviderUtils;
|
||||||
private Handler handler;
|
private Handler handler;
|
||||||
|
|
||||||
|
private Marker.Id markerId;
|
||||||
private Marker marker;
|
private Marker marker;
|
||||||
|
|
||||||
private MarkerDetailFragmentBinding viewBinding;
|
private MarkerDetailFragmentBinding viewBinding;
|
||||||
@@ -104,7 +106,7 @@ public class MarkerDetailFragment extends Fragment {
|
|||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
Marker.Id markerId = getArguments().getParcelable(KEY_MARKER_ID);
|
markerId = getArguments().getParcelable(KEY_MARKER_ID);
|
||||||
if (markerId == null) {
|
if (markerId == null) {
|
||||||
Log.d(TAG, "invalid marker id");
|
Log.d(TAG, "invalid marker id");
|
||||||
getParentFragmentManager().popBackStack();
|
getParentFragmentManager().popBackStack();
|
||||||
@@ -153,6 +155,13 @@ public class MarkerDetailFragment extends Fragment {
|
|||||||
viewBinding = null;
|
viewBinding = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
markerId = null;
|
||||||
|
marker = null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setMenuVisibility(boolean menuVisible) {
|
public void setMenuVisibility(boolean menuVisible) {
|
||||||
super.setMenuVisibility(menuVisible);
|
super.setMenuVisibility(menuVisible);
|
||||||
@@ -182,36 +191,40 @@ public class MarkerDetailFragment extends Fragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
Marker.Id markerId = getArguments().getParcelable(KEY_MARKER_ID); //TODO Should only happen in onCreate?
|
|
||||||
FragmentActivity fragmentActivity = getActivity();
|
FragmentActivity fragmentActivity = getActivity();
|
||||||
Intent intent;
|
|
||||||
switch (item.getItemId()) {
|
if (item.getItemId() == R.id.marker_detail_show_on_map) {
|
||||||
case R.id.marker_detail_show_on_map:
|
|
||||||
IntentUtils.showCoordinateOnMap(getContext(), marker);
|
IntentUtils.showCoordinateOnMap(getContext(), marker);
|
||||||
return true;
|
return true;
|
||||||
case R.id.marker_detail_edit:
|
}
|
||||||
intent = IntentUtils.newIntent(fragmentActivity, MarkerEditActivity.class)
|
|
||||||
|
if (item.getItemId() == R.id.marker_detail_edit) {
|
||||||
|
Intent intent = IntentUtils.newIntent(fragmentActivity, MarkerEditActivity.class)
|
||||||
.putExtra(MarkerEditActivity.EXTRA_MARKER_ID, markerId);
|
.putExtra(MarkerEditActivity.EXTRA_MARKER_ID, markerId);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
case R.id.marker_detail_share:
|
}
|
||||||
|
|
||||||
|
if (item.getItemId() == R.id.marker_detail_share) {
|
||||||
if (marker.hasPhoto()) {
|
if (marker.hasPhoto()) {
|
||||||
intent = IntentUtils.newShareImageIntent(getContext(), marker.getPhotoURI());
|
Intent intent = IntentUtils.newShareFileIntent(getContext(), marker.getId());
|
||||||
intent = Intent.createChooser(intent, null);
|
intent = Intent.createChooser(intent, null);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
case R.id.marker_detail_delete:
|
}
|
||||||
|
|
||||||
|
if (item.getItemId() == R.id.marker_detail_delete) {
|
||||||
DeleteMarkerDialogFragment.showDialog(getChildFragmentManager(), markerId);
|
DeleteMarkerDialogFragment.showDialog(getChildFragmentManager(), markerId);
|
||||||
return true;
|
return true;
|
||||||
default:
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateMarker(boolean refresh) {
|
private void updateMarker(boolean refresh) {
|
||||||
if (refresh || marker == null) {
|
if (refresh || marker == null) {
|
||||||
marker = contentProviderUtils.getMarker(getArguments().getParcelable(KEY_MARKER_ID)); //TODO Should only happen in onCreate?
|
marker = contentProviderUtils.getMarker(markerId);
|
||||||
if (marker == null) {
|
if (marker == null) {
|
||||||
Log.d(TAG, "marker is null");
|
Log.d(TAG, "marker is null");
|
||||||
getParentFragmentManager().popBackStack();
|
getParentFragmentManager().popBackStack();
|
||||||
|
|||||||
@@ -135,7 +135,6 @@ public class StatisticsRecordedFragment extends Fragment {
|
|||||||
// Set activity type
|
// Set activity type
|
||||||
{
|
{
|
||||||
String trackIconValue = TrackIconUtils.getIconValue(getContext(), category);
|
String trackIconValue = TrackIconUtils.getIconValue(getContext(), category);
|
||||||
viewBinding.statsActivityTypeIcon.setEnabled(false);
|
|
||||||
viewBinding.statsActivityTypeIcon.setImageDrawable(ContextCompat.getDrawable(getContext(), TrackIconUtils.getIconDrawable(trackIconValue)));
|
viewBinding.statsActivityTypeIcon.setImageDrawable(ContextCompat.getDrawable(getContext(), TrackIconUtils.getIconDrawable(trackIconValue)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,17 +176,24 @@ public class StatisticsRecordedFragment extends Fragment {
|
|||||||
viewBinding.statsMovingSpeedUnit.setText(parts.second);
|
viewBinding.statsMovingSpeedUnit.setText(parts.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set elevation gain
|
// Set elevation gain and loss
|
||||||
{
|
{
|
||||||
// Make elevation visible?
|
// Make elevation visible?
|
||||||
boolean showElevation = PreferencesUtils.isShowStatsElevation(getContext());
|
boolean showElevation = PreferencesUtils.isShowStatsElevation(getContext());
|
||||||
viewBinding.statsElevationGroup.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
viewBinding.statsElevationGroup.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
Float elevationGain_m = trackStatistics != null ? trackStatistics.getTotalElevationGain() : null;
|
Float elevationGain_m = trackStatistics != null ? trackStatistics.getTotalElevationGain() : null;
|
||||||
|
Float elevationLoss_m = trackStatistics != null ? trackStatistics.getTotalElevationLoss() : null;
|
||||||
|
|
||||||
Pair<String, String> parts = StringUtils.formatElevation(getContext(), elevationGain_m, metricUnits);
|
Pair<String, String> parts;
|
||||||
|
|
||||||
|
parts = StringUtils.formatElevation(getContext(), elevationGain_m, metricUnits);
|
||||||
viewBinding.statsElevationGainValue.setText(parts.first);
|
viewBinding.statsElevationGainValue.setText(parts.first);
|
||||||
viewBinding.statsElevationGainUnit.setText(parts.second);
|
viewBinding.statsElevationGainUnit.setText(parts.second);
|
||||||
|
|
||||||
|
parts = StringUtils.formatElevation(getContext(), elevationLoss_m, metricUnits);
|
||||||
|
viewBinding.statsElevationLossValue.setText(parts.first);
|
||||||
|
viewBinding.statsElevationLossUnit.setText(parts.second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,6 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
|||||||
private StatisticsRecordingBinding viewBinding;
|
private StatisticsRecordingBinding viewBinding;
|
||||||
|
|
||||||
private SensorsAdapter sensorsAdapter;
|
private SensorsAdapter sensorsAdapter;
|
||||||
private RecyclerView sensorsRecyclerView;
|
|
||||||
|
|
||||||
public static Fragment newInstance() {
|
public static Fragment newInstance() {
|
||||||
return new StatisticsRecordingFragment();
|
return new StatisticsRecordingFragment();
|
||||||
@@ -112,7 +111,7 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
|||||||
viewBinding.statsActivityTypeIcon.setOnClickListener(v -> ((TrackRecordingActivity) getActivity()).chooseActivityType(category));
|
viewBinding.statsActivityTypeIcon.setOnClickListener(v -> ((TrackRecordingActivity) getActivity()).chooseActivityType(category));
|
||||||
|
|
||||||
sensorsAdapter = new SensorsAdapter(getContext());
|
sensorsAdapter = new SensorsAdapter(getContext());
|
||||||
sensorsRecyclerView = viewBinding.statsSensorsRecyclerView;
|
RecyclerView sensorsRecyclerView = viewBinding.statsSensorsRecyclerView;
|
||||||
sensorsRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2));
|
sensorsRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2));
|
||||||
sensorsRecyclerView.setAdapter(sensorsAdapter);
|
sensorsRecyclerView.setAdapter(sensorsAdapter);
|
||||||
}
|
}
|
||||||
@@ -262,7 +261,7 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Tries to fetch most recent {@link SensorDataSet} from {@link de.dennisguse.opentracks.services.TrackRecordingService}.
|
* Tries to fetch most recent {@link SensorDataSet} from {@link de.dennisguse.opentracks.services.TrackRecordingService}.
|
||||||
* Also sets elevation gain.
|
* Also sets elevation gain and loss.
|
||||||
*/
|
*/
|
||||||
private void updateSensorDataUI() {
|
private void updateSensorDataUI() {
|
||||||
TrackRecordingServiceInterface trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
|
TrackRecordingServiceInterface trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
|
||||||
@@ -285,13 +284,14 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
|||||||
sensorsAdapter.swapData(sensorDataList);
|
sensorsAdapter.swapData(sensorDataList);
|
||||||
setSpeedSensorData(sensorDataSet);
|
setSpeedSensorData(sensorDataSet);
|
||||||
}
|
}
|
||||||
|
//TODO Check if we can distribute the total elevation gain and loss via trackStatistics instead of doing some computation in the UI layer.
|
||||||
setTotalElevationGain(trackRecordingService.getElevationGain_m());
|
setTotalElevationGain(trackRecordingService.getElevationGain_m());
|
||||||
|
setTotalElevationLoss(trackRecordingService.getElevationLoss_m());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set elevation gain
|
// Set elevation gain
|
||||||
private void setTotalElevationGain(Float elevationGain_m) {
|
private void setTotalElevationGain(Float elevationGain_m) {
|
||||||
//TODO Check if we can distribute the total elevation gain via trackStatistics instead of doing some computation in the UI layer.
|
|
||||||
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
||||||
|
|
||||||
Float totalElevationGain = elevationGain_m;
|
Float totalElevationGain = elevationGain_m;
|
||||||
@@ -309,6 +309,25 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
|||||||
viewBinding.statsElevationGainUnit.setText(parts.second);
|
viewBinding.statsElevationGainUnit.setText(parts.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set elevation loss
|
||||||
|
private void setTotalElevationLoss(Float elevationLoss_m) {
|
||||||
|
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
||||||
|
|
||||||
|
Float totalElevationLoss = elevationLoss_m;
|
||||||
|
|
||||||
|
if (lastTrackStatistics != null && lastTrackStatistics.hasTotalElevationLoss()) {
|
||||||
|
if (elevationLoss_m == null) {
|
||||||
|
totalElevationLoss = lastTrackStatistics.getTotalElevationLoss();
|
||||||
|
} else {
|
||||||
|
totalElevationLoss += lastTrackStatistics.getTotalElevationLoss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Pair<String, String> parts = StringUtils.formatElevation(getContext(), totalElevationLoss, metricUnits);
|
||||||
|
viewBinding.statsElevationLossValue.setText(parts.first);
|
||||||
|
viewBinding.statsElevationLossUnit.setText(parts.second);
|
||||||
|
}
|
||||||
|
|
||||||
private void setSpeedSensorData(SensorDataSet sensorDataSet) {
|
private void setSpeedSensorData(SensorDataSet sensorDataSet) {
|
||||||
if (sensorDataSet != null && sensorDataSet.getCyclingSpeed() != null) {
|
if (sensorDataSet != null && sensorDataSet.getCyclingSpeed() != null) {
|
||||||
SensorDataCycling.Speed data = sensorDataSet.getCyclingSpeed();
|
SensorDataCycling.Speed data = sensorDataSet.getCyclingSpeed();
|
||||||
@@ -379,7 +398,7 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
|||||||
viewBinding.statsMovingSpeedUnit.setText(parts.second);
|
viewBinding.statsMovingSpeedUnit.setText(parts.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set elevation (gain)
|
// Set elevation gain and loss
|
||||||
{
|
{
|
||||||
// Make elevation visible?
|
// Make elevation visible?
|
||||||
boolean showElevation = PreferencesUtils.isShowStatsElevation(getContext());
|
boolean showElevation = PreferencesUtils.isShowStatsElevation(getContext());
|
||||||
|
|||||||
@@ -54,14 +54,14 @@ public class ErrorListDialog extends DialogFragment {
|
|||||||
outState.putStringArrayList(EXTRA_ERROR_LIST, errorList);
|
outState.putStringArrayList(EXTRA_ERROR_LIST, errorList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
final String[] tracks = errorList.stream().toArray(String[]::new);
|
final String[] tracks = errorList.toArray(new String[0]);
|
||||||
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity())
|
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity())
|
||||||
.setTitle(title)
|
.setTitle(title)
|
||||||
.setItems(tracks, null)
|
.setItems(tracks, null)
|
||||||
.setPositiveButton(R.string.generic_ok, (dialog, which) -> dismiss());
|
.setPositiveButton(R.string.generic_ok, (dialog, which) -> dismiss());
|
||||||
AlertDialog dialog = alertDialogBuilder.create();
|
return alertDialogBuilder.create();
|
||||||
return dialog;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
|||||||
|
|
||||||
private ExportServiceResultReceiver resultReceiver;
|
private ExportServiceResultReceiver resultReceiver;
|
||||||
|
|
||||||
private String directoryDisplayName;
|
|
||||||
private List<String> directoryFiles;
|
private List<String> directoryFiles;
|
||||||
|
|
||||||
private int trackExportSuccessCount;
|
private int trackExportSuccessCount;
|
||||||
@@ -143,7 +142,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
|||||||
contentProviderUtils = new ContentProviderUtils(this);
|
contentProviderUtils = new ContentProviderUtils(this);
|
||||||
|
|
||||||
DocumentFile documentFile = DocumentFile.fromTreeUri(this, directoryUri);
|
DocumentFile documentFile = DocumentFile.fromTreeUri(this, directoryUri);
|
||||||
directoryDisplayName = FileUtils.getPath(documentFile);
|
String directoryDisplayName = FileUtils.getPath(documentFile);
|
||||||
|
|
||||||
viewBinding.toolbar.toolbar.setTitle(getString(R.string.export_progress_message, directoryDisplayName));
|
viewBinding.toolbar.toolbar.setTitle(getString(R.string.export_progress_message, directoryDisplayName));
|
||||||
|
|
||||||
@@ -269,7 +268,14 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
|||||||
viewBinding.exportProgressTotal.setText("" + trackExportTotalCount);
|
viewBinding.exportProgressTotal.setText("" + trackExportTotalCount);
|
||||||
|
|
||||||
viewBinding.exportProgressBar.setProgress((int) ((float) done / (float) trackExportTotalCount * 100f));
|
viewBinding.exportProgressBar.setProgress((int) ((float) done / (float) trackExportTotalCount * 100f));
|
||||||
viewBinding.exportProgressSummary.setText(getString(R.string.export_progress_review, getTotalDone(), trackExportSuccessCount, trackExportOverwrittenCount, trackExportSkippedCount, trackExportErrorCount));
|
viewBinding.exportProgressSummaryNew.setText(String.valueOf(trackExportSuccessCount));
|
||||||
|
viewBinding.exportProgressSummaryOverwrite.setText(String.valueOf(trackExportOverwrittenCount));
|
||||||
|
viewBinding.exportProgressSummarySkip.setText(String.valueOf(trackExportSkippedCount));
|
||||||
|
viewBinding.exportProgressSummaryErrors.setText(String.valueOf(trackExportErrorCount));
|
||||||
|
viewBinding.exportProgressSummaryNewGroup.setVisibility(trackExportSuccessCount > 0 ? View.VISIBLE : View.GONE);
|
||||||
|
viewBinding.exportProgressSummaryOverwriteGroup.setVisibility(trackExportOverwrittenCount > 0 ? View.VISIBLE : View.GONE);
|
||||||
|
viewBinding.exportProgressSummarySkipGroup.setVisibility(trackExportSkippedCount > 0 ? View.VISIBLE : View.GONE);
|
||||||
|
viewBinding.exportProgressSummaryErrorsGroup.setVisibility(trackExportErrorCount > 0 ? View.VISIBLE : View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onExportCompleted(Track.Id trackId) {
|
private void onExportCompleted(Track.Id trackId) {
|
||||||
@@ -360,7 +366,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
|||||||
public boolean resolve() {
|
public boolean resolve() {
|
||||||
if (autoConflict == CONFLICT_NONE) {
|
if (autoConflict == CONFLICT_NONE) {
|
||||||
Track track = contentProviderUtils.getTrack(trackId);
|
Track track = contentProviderUtils.getTrack(trackId);
|
||||||
viewBinding.exportProgressAlertIcon.setImageDrawable(getDrawable(R.drawable.ic_report_problem_24));
|
viewBinding.exportProgressAlertIcon.setImageDrawable(ContextCompat.getDrawable(ExportActivity.this, R.drawable.ic_report_problem_24));
|
||||||
viewBinding.exportProgressAlertMsg.setText(getString(R.string.export_track_already_exists_msg, track.getName()));
|
viewBinding.exportProgressAlertMsg.setText(getString(R.string.export_track_already_exists_msg, track.getName()));
|
||||||
setConflictVisibility(View.VISIBLE);
|
setConflictVisibility(View.VISIBLE);
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ public class FileTrackExporter implements TrackExporter {
|
|||||||
boolean isLastLocationValid = false;
|
boolean isLastLocationValid = false;
|
||||||
TrackPoint lastTrackPoint = null;
|
TrackPoint lastTrackPoint = null;
|
||||||
|
|
||||||
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false)) {
|
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null)) {
|
||||||
|
|
||||||
while (trackPointIterator.hasNext()) {
|
while (trackPointIterator.hasNext()) {
|
||||||
if (Thread.interrupted()) {
|
if (Thread.interrupted()) {
|
||||||
|
|||||||
@@ -197,10 +197,9 @@ public class GpxTrackWriter implements TrackWriter {
|
|||||||
|
|
||||||
printWriter.println("<time>" + StringUtils.formatDateTimeIso8601(trackPoint.getTime()) + "</time>");
|
printWriter.println("<time>" + StringUtils.formatDateTimeIso8601(trackPoint.getTime()) + "</time>");
|
||||||
|
|
||||||
if (trackPoint.hasSpeed() || trackPoint.hasHeartRate() || trackPoint.hasCyclingCadence()) {
|
if (trackPoint.hasSpeed() || trackPoint.hasHeartRate() || trackPoint.hasCyclingCadence() || trackPoint.hasElevationGain() || trackPoint.hasElevationLoss()) {
|
||||||
printWriter.println("<extensions><gpxtpx:TrackPointExtension>");
|
printWriter.println("<extensions><gpxtpx:TrackPointExtension>");
|
||||||
|
|
||||||
|
|
||||||
if (trackPoint.hasSpeed()) {
|
if (trackPoint.hasSpeed()) {
|
||||||
printWriter.println("<gpxtpx:speed>" + SPEED_FORMAT.format(trackPoint.getSpeed()) + "</gpxtpx:speed>");
|
printWriter.println("<gpxtpx:speed>" + SPEED_FORMAT.format(trackPoint.getSpeed()) + "</gpxtpx:speed>");
|
||||||
}
|
}
|
||||||
@@ -213,6 +212,14 @@ public class GpxTrackWriter implements TrackWriter {
|
|||||||
printWriter.println("<gpxtpx:cad>" + HEARTRATE_FORMAT.format(trackPoint.getCyclingCadence_rpm()) + "</gpxtpx:cad>");
|
printWriter.println("<gpxtpx:cad>" + HEARTRATE_FORMAT.format(trackPoint.getCyclingCadence_rpm()) + "</gpxtpx:cad>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (trackPoint.hasElevationGain()) {
|
||||||
|
printWriter.println("<opentracks:gain>" + ELEVATION_FORMAT.format(trackPoint.getElevationGain()) + "</opentracks:gain>");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trackPoint.hasElevationLoss()) {
|
||||||
|
printWriter.println("<opentracks:loss>" + ELEVATION_FORMAT.format(trackPoint.getElevationLoss()) + "</opentracks:loss>");
|
||||||
|
}
|
||||||
|
|
||||||
printWriter.println("</gpxtpx:TrackPointExtension></extensions>");
|
printWriter.println("</gpxtpx:TrackPointExtension></extensions>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
package de.dennisguse.opentracks.io.file.exporter;
|
package de.dennisguse.opentracks.io.file.exporter;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
@@ -51,6 +50,7 @@ public class KmlTrackWriter implements TrackWriter {
|
|||||||
public static final String EXTENDED_DATA_TYPE_HEART_RATE = "heart_rate";
|
public static final String EXTENDED_DATA_TYPE_HEART_RATE = "heart_rate";
|
||||||
public static final String EXTENDED_DATA_TYPE_POWER = "power";
|
public static final String EXTENDED_DATA_TYPE_POWER = "power";
|
||||||
public static final String EXTENDED_DATA_TYPE_ELEVATION_GAIN = "elevation_gain";
|
public static final String EXTENDED_DATA_TYPE_ELEVATION_GAIN = "elevation_gain";
|
||||||
|
public static final String EXTENDED_DATA_TYPE_ELEVATION_LOSS = "elevation_loss";
|
||||||
|
|
||||||
private static final String MARKER_ICON = "http://maps.google.com/mapfiles/kml/pushpin/blue-pushpin.png";
|
private static final String MARKER_ICON = "http://maps.google.com/mapfiles/kml/pushpin/blue-pushpin.png";
|
||||||
private static final String START_ICON = "http://maps.google.com/mapfiles/kml/paddle/grn-circle.png";
|
private static final String START_ICON = "http://maps.google.com/mapfiles/kml/paddle/grn-circle.png";
|
||||||
@@ -70,6 +70,7 @@ public class KmlTrackWriter implements TrackWriter {
|
|||||||
private final List<Float> cadenceList = new ArrayList<>();
|
private final List<Float> cadenceList = new ArrayList<>();
|
||||||
private final List<Float> heartRateList = new ArrayList<>();
|
private final List<Float> heartRateList = new ArrayList<>();
|
||||||
private final List<Float> elevationGainList = new ArrayList<>();
|
private final List<Float> elevationGainList = new ArrayList<>();
|
||||||
|
private final List<Float> elevationLossList = new ArrayList<>();
|
||||||
|
|
||||||
private TrackPoint startTrackPoint;
|
private TrackPoint startTrackPoint;
|
||||||
|
|
||||||
@@ -167,7 +168,7 @@ public class KmlTrackWriter implements TrackWriter {
|
|||||||
@Override
|
@Override
|
||||||
public void writeMarker(Marker marker) {
|
public void writeMarker(Marker marker) {
|
||||||
if (printWriter != null && exportTrackDetail) {
|
if (printWriter != null && exportTrackDetail) {
|
||||||
boolean existsPhoto = FileUtils.getPhotoFileIfExists(context, marker.getTrackId(), marker.getPhotoURI()) != null;
|
boolean existsPhoto = FileUtils.buildInternalPhotoFile(context, marker.getTrackId(), marker.getPhotoURI()) != null;
|
||||||
if (marker.hasPhoto() && exportPhotos && existsPhoto) {
|
if (marker.hasPhoto() && exportPhotos && existsPhoto) {
|
||||||
float heading = getHeading(marker.getTrackId(), marker.getLocation());
|
float heading = getHeading(marker.getTrackId(), marker.getLocation());
|
||||||
writePhotoOverlay(marker, heading);
|
writePhotoOverlay(marker, heading);
|
||||||
@@ -262,6 +263,9 @@ public class KmlTrackWriter implements TrackWriter {
|
|||||||
if (elevationGainList.size() > 0) {
|
if (elevationGainList.size() > 0) {
|
||||||
writeSimpleArrayData(elevationGainList, EXTENDED_DATA_TYPE_ELEVATION_GAIN);
|
writeSimpleArrayData(elevationGainList, EXTENDED_DATA_TYPE_ELEVATION_GAIN);
|
||||||
}
|
}
|
||||||
|
if (elevationLossList.size() > 0) {
|
||||||
|
writeSimpleArrayData(elevationLossList, EXTENDED_DATA_TYPE_ELEVATION_LOSS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
printWriter.println("</SchemaData>");
|
printWriter.println("</SchemaData>");
|
||||||
printWriter.println("</ExtendedData>");
|
printWriter.println("</ExtendedData>");
|
||||||
@@ -295,6 +299,9 @@ public class KmlTrackWriter implements TrackWriter {
|
|||||||
if (trackPoint.hasElevationGain()) {
|
if (trackPoint.hasElevationGain()) {
|
||||||
elevationGainList.add(trackPoint.getElevationGain());
|
elevationGainList.add(trackPoint.getElevationGain());
|
||||||
}
|
}
|
||||||
|
if (trackPoint.hasElevationLoss()) {
|
||||||
|
elevationLossList.add(trackPoint.getElevationLoss());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -391,21 +398,18 @@ public class KmlTrackWriter implements TrackWriter {
|
|||||||
* @param location the location
|
* @param location the location
|
||||||
*/
|
*/
|
||||||
private float getHeading(Track.Id trackId, Location location) {
|
private float getHeading(Track.Id trackId, Location location) {
|
||||||
long trackPointId = contentProviderUtils.getTrackPointId(trackId, location);
|
TrackPoint.Id trackPointId = contentProviderUtils.getTrackPointId(trackId, location);
|
||||||
if (trackPointId == -1L) {
|
if (trackPointId == null) {
|
||||||
return location.getBearing();
|
return location.getBearing();
|
||||||
}
|
}
|
||||||
TrackPoint viewLocation;
|
TrackPoint viewLocation = contentProviderUtils.getLastValidTrackPoint(trackId);
|
||||||
try (Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackPointId, 10, true)) {
|
if (viewLocation != null) {
|
||||||
if (cursor == null || cursor.getCount() == 0) {
|
|
||||||
return location.getBearing();
|
|
||||||
}
|
|
||||||
cursor.moveToPosition(cursor.getCount() - 1);
|
|
||||||
viewLocation = contentProviderUtils.createTrackPoint(cursor);
|
|
||||||
}
|
|
||||||
return viewLocation.bearingTo(location);
|
return viewLocation.bearingTo(location);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return location.getBearing();
|
||||||
|
}
|
||||||
|
|
||||||
private String getCoordinates(Location location, String separator) {
|
private String getCoordinates(Location location, String separator) {
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
builder.append(location.getLongitude()).append(separator).append(location.getLatitude());
|
builder.append(location.getLongitude()).append(separator).append(location.getLatitude());
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ public class KmzTrackExporter implements TrackExporter {
|
|||||||
private final FileTrackExporter fileTrackExporter;
|
private final FileTrackExporter fileTrackExporter;
|
||||||
|
|
||||||
private final boolean exportPhotos;
|
private final boolean exportPhotos;
|
||||||
private Context context;
|
private final Context context;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
@@ -108,7 +108,7 @@ public class KmzTrackExporter implements TrackExporter {
|
|||||||
Marker marker = contentProviderUtils.createMarker(cursor);
|
Marker marker = contentProviderUtils.createMarker(cursor);
|
||||||
if (marker.hasPhoto()) {
|
if (marker.hasPhoto()) {
|
||||||
Uri uriPhoto = marker.getPhotoURI();
|
Uri uriPhoto = marker.getPhotoURI();
|
||||||
boolean existsPhoto = FileUtils.getPhotoFileIfExists(context, track.getId(), uriPhoto) != null;
|
boolean existsPhoto = FileUtils.buildInternalPhotoFile(context, track.getId(), uriPhoto) != null;
|
||||||
if (existsPhoto) {
|
if (existsPhoto) {
|
||||||
addImage(context, zipOutputStream, uriPhoto, marker);
|
addImage(context, zipOutputStream, uriPhoto, marker);
|
||||||
}
|
}
|
||||||
|
|||||||
+40
-28
@@ -29,7 +29,6 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -61,9 +60,6 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
|||||||
|
|
||||||
private static final String TAG = AbstractFileTrackImporter.class.getSimpleName();
|
private static final String TAG = AbstractFileTrackImporter.class.getSimpleName();
|
||||||
|
|
||||||
// The maximum number of buffered locations for bulk-insertion
|
|
||||||
private static final int MAX_BUFFERED_LOCATIONS = 512;
|
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final ContentProviderUtils contentProviderUtils;
|
private final ContentProviderUtils contentProviderUtils;
|
||||||
private final int recordingDistanceInterval;
|
private final int recordingDistanceInterval;
|
||||||
@@ -90,6 +86,8 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
|||||||
protected String markerType;
|
protected String markerType;
|
||||||
protected String photoUrl;
|
protected String photoUrl;
|
||||||
protected String uuid;
|
protected String uuid;
|
||||||
|
protected String gain;
|
||||||
|
protected String loss;
|
||||||
|
|
||||||
// The current track data
|
// The current track data
|
||||||
private TrackData trackData;
|
private TrackData trackData;
|
||||||
@@ -127,18 +125,15 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
|||||||
|
|
||||||
saxParser.parse(inputStream, this);
|
saxParser.parse(inputStream, this);
|
||||||
Log.d(TAG, "Total import time: " + (System.currentTimeMillis() - start) + "ms");
|
Log.d(TAG, "Total import time: " + (System.currentTimeMillis() - start) + "ms");
|
||||||
if (trackIds.size() != 1) {
|
|
||||||
// TODO Multi track is not supported yet.
|
|
||||||
throw new ImportParserException("Multi track not supported");
|
|
||||||
}
|
|
||||||
return trackIds.get(0);
|
return trackIds.get(0);
|
||||||
} catch (IOException | SAXException | ParserConfigurationException e) {
|
} catch (IOException | SAXException | ParserConfigurationException e) {
|
||||||
Log.e(TAG, "Unable to import file", e);
|
Log.e(TAG, "Unable to import file", e);
|
||||||
|
if (trackIds.size() > 0) {
|
||||||
cleanImport();
|
cleanImport();
|
||||||
|
}
|
||||||
throw new ImportParserException(e);
|
throw new ImportParserException(e);
|
||||||
} catch (SQLiteConstraintException e) {
|
} catch (SQLiteConstraintException e) {
|
||||||
Log.e(TAG, "Unable to import file", e);
|
Log.e(TAG, "Unable to import file", e);
|
||||||
cleanImport();
|
|
||||||
throw new ImportAlreadyExistsException(e);
|
throw new ImportAlreadyExistsException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -165,7 +160,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
|||||||
// TODO Should not be necessary anymore?
|
// TODO Should not be necessary anymore?
|
||||||
TrackStatisticsUpdater markerTrackStatisticsUpdater = new TrackStatisticsUpdater(track.getTrackStatistics().getStartTime_ms());
|
TrackStatisticsUpdater markerTrackStatisticsUpdater = new TrackStatisticsUpdater(track.getTrackStatistics().getStartTime_ms());
|
||||||
|
|
||||||
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false)) {
|
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null)) {
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (marker == null) {
|
if (marker == null) {
|
||||||
@@ -228,17 +223,12 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
|||||||
*/
|
*/
|
||||||
protected void onTrackStart() throws SAXException {
|
protected void onTrackStart() throws SAXException {
|
||||||
trackData = new TrackData();
|
trackData = new TrackData();
|
||||||
Uri uri = contentProviderUtils.insertTrack(trackData.track);
|
|
||||||
Track.Id trackId = new Track.Id(Long.parseLong(uri.getLastPathSegment()));
|
|
||||||
trackIds.add(trackId);
|
|
||||||
trackData.track.setId(trackId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On track end.
|
* On track end.
|
||||||
*/
|
*/
|
||||||
protected void onTrackEnd() {
|
protected void onTrackEnd() {
|
||||||
flushLocations(trackData);
|
|
||||||
if (name != null) {
|
if (name != null) {
|
||||||
trackData.track.setName(name);
|
trackData.track.setName(name);
|
||||||
}
|
}
|
||||||
@@ -268,17 +258,27 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
|||||||
}
|
}
|
||||||
trackData.track.setTrackStatistics(trackData.trackStatisticsUpdater.getTrackStatistics());
|
trackData.track.setTrackStatistics(trackData.trackStatisticsUpdater.getTrackStatistics());
|
||||||
|
|
||||||
try {
|
Track track = contentProviderUtils.getTrack(trackData.track.getUuid());
|
||||||
contentProviderUtils.updateTrack(trackData.track);
|
if (track != null) {
|
||||||
} catch (SQLiteConstraintException e) {
|
|
||||||
if (PreferencesUtils.getPreventReimportTracks(context)) {
|
if (PreferencesUtils.getPreventReimportTracks(context)) {
|
||||||
throw e;
|
throw new ImportAlreadyExistsException(context.getString(R.string.import_prevent_reimport));
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO This is a workaround until we have proper UI.
|
//TODO This is a workaround until we have proper UI.
|
||||||
trackData.track.setUuid(UUID.randomUUID());
|
trackData.track.setUuid(UUID.randomUUID());
|
||||||
contentProviderUtils.updateTrack(trackData.track);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (trackIds.size() > 0) {
|
||||||
|
// TODO Multi track is not supported yet.
|
||||||
|
cleanImport();
|
||||||
|
throw new ImportParserException("Multi track not supported");
|
||||||
|
}
|
||||||
|
Uri uri = contentProviderUtils.insertTrack(trackData.track);
|
||||||
|
Track.Id trackId = new Track.Id(Long.parseLong(uri.getLastPathSegment()));
|
||||||
|
trackIds.add(trackId);
|
||||||
|
trackData.track.setId(trackId);
|
||||||
|
|
||||||
|
flushLocations(trackData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -303,7 +303,8 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
|||||||
TrackPoint trackPoint = createTrackPoint();
|
TrackPoint trackPoint = createTrackPoint();
|
||||||
|
|
||||||
if (!LocationUtils.isValidLocation(trackPoint.getLocation())) {
|
if (!LocationUtils.isValidLocation(trackPoint.getLocation())) {
|
||||||
throw new SAXException(createErrorMessage("Invalid location detected: " + trackPoint));
|
Log.w(TAG, "Marker with invalid coordinates ignored: " + trackPoint.getLocation());
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
Marker marker = new Marker(trackPoint.getLocation());
|
Marker marker = new Marker(trackPoint.getLocation());
|
||||||
|
|
||||||
@@ -468,6 +469,21 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (gain != null) {
|
||||||
|
try {
|
||||||
|
trackPoint.setElevationGain(Float.parseFloat(gain));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse elevation gain: %s", gain)), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (loss != null) {
|
||||||
|
try {
|
||||||
|
trackPoint.setElevationLoss(Float.parseFloat(loss));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse elevation loss: %s", loss)), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return trackPoint;
|
return trackPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -482,13 +498,9 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
|||||||
}
|
}
|
||||||
trackData.trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
|
trackData.trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
|
||||||
|
|
||||||
trackData.bufferedTrackPoints[trackData.numBufferedTrackPoints] = trackPoint;
|
trackData.bufferedTrackPoints.add(trackPoint);
|
||||||
trackData.numBufferedTrackPoints++;
|
trackData.numBufferedTrackPoints++;
|
||||||
trackData.numberOfLocations++;
|
trackData.numberOfLocations++;
|
||||||
|
|
||||||
if (trackData.numBufferedTrackPoints >= MAX_BUFFERED_LOCATIONS) {
|
|
||||||
flushLocations(trackData);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -500,7 +512,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
|||||||
if (data.numBufferedTrackPoints <= 0) {
|
if (data.numBufferedTrackPoints <= 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
contentProviderUtils.bulkInsertTrackPoint(Arrays.copyOfRange(data.bufferedTrackPoints, 0, data.numBufferedTrackPoints), data.track.getId());
|
contentProviderUtils.bulkInsertTrackPoint(data.bufferedTrackPoints, data.track.getId());
|
||||||
data.numBufferedTrackPoints = 0;
|
data.numBufferedTrackPoints = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -537,7 +549,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
|||||||
final long importTime = System.currentTimeMillis();
|
final long importTime = System.currentTimeMillis();
|
||||||
|
|
||||||
// The buffered locations
|
// The buffered locations
|
||||||
final TrackPoint[] bufferedTrackPoints = new TrackPoint[MAX_BUFFERED_LOCATIONS];
|
final List<TrackPoint> bufferedTrackPoints = new ArrayList<>();
|
||||||
|
|
||||||
// The number of buffered locations
|
// The number of buffered locations
|
||||||
int numBufferedTrackPoints = 0;
|
int numBufferedTrackPoints = 0;
|
||||||
|
|||||||
@@ -54,6 +54,9 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
|||||||
private static final String TAG_EXTENSION_HEARTRATE = "gpxtpx:hr";
|
private static final String TAG_EXTENSION_HEARTRATE = "gpxtpx:hr";
|
||||||
private static final String TAG_EXTENSION_CADENCE = "gpxtpx:cad";
|
private static final String TAG_EXTENSION_CADENCE = "gpxtpx:cad";
|
||||||
|
|
||||||
|
private static final String TAG_EXTENSION_GAIN = "opentracks:gain";
|
||||||
|
private static final String TAG_EXTENSION_LOSS = "opentracks:loss";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
@@ -151,6 +154,16 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
|||||||
uuid = content.trim();
|
uuid = content.trim();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case TAG_EXTENSION_GAIN:
|
||||||
|
if (content != null) {
|
||||||
|
gain = content.trim();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TAG_EXTENSION_LOSS:
|
||||||
|
if (content != null) {
|
||||||
|
loss = content.trim();
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset element content
|
// Reset element content
|
||||||
@@ -176,6 +189,8 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
|||||||
altitude = null;
|
altitude = null;
|
||||||
time = null;
|
time = null;
|
||||||
speed = null;
|
speed = null;
|
||||||
|
gain = null;
|
||||||
|
loss = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package de.dennisguse.opentracks.io.file.importer;
|
package de.dennisguse.opentracks.io.file.importer;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
@@ -24,13 +25,16 @@ import android.widget.Toast;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.documentfile.provider.DocumentFile;
|
import androidx.documentfile.provider.DocumentFile;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
|
import de.dennisguse.opentracks.TrackRecordedActivity;
|
||||||
import de.dennisguse.opentracks.databinding.ImportActivityBinding;
|
import de.dennisguse.opentracks.databinding.ImportActivityBinding;
|
||||||
import de.dennisguse.opentracks.io.file.ErrorListDialog;
|
import de.dennisguse.opentracks.io.file.ErrorListDialog;
|
||||||
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An activity to import files from the external storage.
|
* An activity to import files from the external storage.
|
||||||
@@ -126,7 +130,9 @@ public class ImportActivity extends FragmentActivity {
|
|||||||
private void initViews() {
|
private void initViews() {
|
||||||
viewBinding.importProgressDone.setText("0");
|
viewBinding.importProgressDone.setText("0");
|
||||||
viewBinding.importProgressTotal.setText("0");
|
viewBinding.importProgressTotal.setText("0");
|
||||||
viewBinding.importProgressSummary.setText(getString(R.string.import_progress_review, 0, 0, 0, 0));
|
viewBinding.importProgressSummaryOk.setText("0");
|
||||||
|
viewBinding.importProgressSummaryExists.setText("0");
|
||||||
|
viewBinding.importProgressSummaryErrors.setText("0");
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getTotalDone() {
|
private int getTotalDone() {
|
||||||
@@ -140,7 +146,12 @@ public class ImportActivity extends FragmentActivity {
|
|||||||
viewBinding.importProgressTotal.setText("" + summary.getTotalCount());
|
viewBinding.importProgressTotal.setText("" + summary.getTotalCount());
|
||||||
|
|
||||||
viewBinding.importProgressBar.setProgress((int) ((float) done / (float) summary.getTotalCount() * 100f));
|
viewBinding.importProgressBar.setProgress((int) ((float) done / (float) summary.getTotalCount() * 100f));
|
||||||
viewBinding.importProgressSummary.setText(getString(R.string.import_progress_review, getTotalDone(), summary.getSuccessCount(), summary.getExistsCount(), summary.getErrorCount()));
|
viewBinding.importProgressSummaryOk.setText(String.valueOf(summary.getSuccessCount()));
|
||||||
|
viewBinding.importProgressSummaryExists.setText(String.valueOf(summary.getExistsCount()));
|
||||||
|
viewBinding.importProgressSummaryErrors.setText(String.valueOf(summary.getErrorCount()));
|
||||||
|
viewBinding.importProgressSummaryOkGroup.setVisibility(summary.getSuccessCount() > 0 ? View.VISIBLE : View.GONE);
|
||||||
|
viewBinding.importProgressSummaryExistsGroup.setVisibility(summary.getExistsCount() > 0 ? View.VISIBLE : View.GONE);
|
||||||
|
viewBinding.importProgressSummaryErrorsGroup.setVisibility(summary.getErrorCount() > 0 ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
if (done == summary.getTotalCount()) {
|
if (done == summary.getTotalCount()) {
|
||||||
onImportEnded();
|
onImportEnded();
|
||||||
@@ -148,6 +159,9 @@ public class ImportActivity extends FragmentActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onImportEnded() {
|
private void onImportEnded() {
|
||||||
|
viewBinding.importProgressAlertMsg.setVisibility(View.VISIBLE);
|
||||||
|
viewBinding.importProgressAlertIcon.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
viewBinding.importProgressRightButton.setVisibility(View.VISIBLE);
|
viewBinding.importProgressRightButton.setVisibility(View.VISIBLE);
|
||||||
viewBinding.importProgressRightButton.setText(getString(R.string.generic_ok));
|
viewBinding.importProgressRightButton.setText(getString(R.string.generic_ok));
|
||||||
viewBinding.importProgressRightButton.setOnClickListener((view) -> {
|
viewBinding.importProgressRightButton.setOnClickListener((view) -> {
|
||||||
@@ -155,19 +169,37 @@ public class ImportActivity extends FragmentActivity {
|
|||||||
finish();
|
finish();
|
||||||
});
|
});
|
||||||
|
|
||||||
viewBinding.importProgressAlertMsg.setVisibility(View.VISIBLE);
|
|
||||||
viewBinding.importProgressAlertIcon.setVisibility(View.VISIBLE);
|
|
||||||
if (summary.getErrorCount() > 0) {
|
if (summary.getErrorCount() > 0) {
|
||||||
|
toggleUIEndWithErrors();
|
||||||
|
} else {
|
||||||
|
toggleUIEndOk();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toggleUIEndWithErrors() {
|
||||||
viewBinding.importProgressLeftButton.setVisibility(View.VISIBLE);
|
viewBinding.importProgressLeftButton.setVisibility(View.VISIBLE);
|
||||||
viewBinding.importProgressLeftButton.setText(getString(R.string.generic_show_errors));
|
viewBinding.importProgressLeftButton.setText(getString(R.string.generic_show_errors));
|
||||||
viewBinding.importProgressLeftButton.setOnClickListener((view) -> ErrorListDialog.showDialog(getSupportFragmentManager(), getString(R.string.import_error_list_dialog_title), summary.getFileErrors()));
|
viewBinding.importProgressLeftButton.setOnClickListener((view) -> ErrorListDialog.showDialog(getSupportFragmentManager(), getString(R.string.import_error_list_dialog_title), summary.getFileErrors()));
|
||||||
viewBinding.importProgressAlertIcon.setImageDrawable(getDrawable(R.drawable.ic_report_problem_24));
|
viewBinding.importProgressAlertIcon.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_report_problem_24));
|
||||||
String msg = getResources().getQuantityString(R.plurals.generic_completed_with_errors, summary.getErrorCount(), summary.getErrorCount());
|
viewBinding.importProgressAlertMsg.setText(getResources().getQuantityString(R.plurals.generic_completed_with_errors, summary.getErrorCount(), summary.getErrorCount()));
|
||||||
viewBinding.importProgressAlertMsg.setText(msg);
|
}
|
||||||
|
|
||||||
|
private void toggleUIEndOk() {
|
||||||
|
viewBinding.importProgressAlertIcon.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_dialog_success_24dp));
|
||||||
|
viewBinding.importProgressAlertMsg.setText(getString(R.string.generic_completed));
|
||||||
|
|
||||||
|
if (summary.getTotalCount() == 1 && summary.getImportedTrackIds().size() == 1) {
|
||||||
|
viewBinding.importProgressLeftButton.setVisibility(View.VISIBLE);
|
||||||
|
viewBinding.importProgressLeftButton.setText(getString(R.string.generic_open_track));
|
||||||
|
viewBinding.importProgressLeftButton.setOnClickListener((view) -> {
|
||||||
|
Intent newIntent = IntentUtils.newIntent(ImportActivity.this, TrackRecordedActivity.class)
|
||||||
|
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, summary.getImportedTrackIds().get(0));
|
||||||
|
startActivity(newIntent);
|
||||||
|
getViewModelStore().clear();
|
||||||
|
finish();
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
viewBinding.importProgressLeftButton.setVisibility(View.GONE);
|
viewBinding.importProgressLeftButton.setVisibility(View.GONE);
|
||||||
viewBinding.importProgressAlertIcon.setImageDrawable(getDrawable(R.drawable.ic_dialog_success_24dp));
|
|
||||||
viewBinding.importProgressAlertMsg.setText(getString(R.string.generic_completed));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+4
@@ -4,4 +4,8 @@ class ImportAlreadyExistsException extends RuntimeException {
|
|||||||
public ImportAlreadyExistsException(Exception e) {
|
public ImportAlreadyExistsException(Exception e) {
|
||||||
super(e);
|
super(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ImportAlreadyExistsException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
import de.dennisguse.opentracks.util.FileUtils;
|
import de.dennisguse.opentracks.util.FileUtils;
|
||||||
|
|
||||||
public class ImportViewModel extends AndroidViewModel implements ImportServiceResultReceiver.Receiver {
|
public class ImportViewModel extends AndroidViewModel implements ImportServiceResultReceiver.Receiver {
|
||||||
@@ -65,6 +66,7 @@ public class ImportViewModel extends AndroidViewModel implements ImportServiceRe
|
|||||||
throw new RuntimeException(TAG + ": onReceiveResult resultData NULL");
|
throw new RuntimeException(TAG + ": onReceiveResult resultData NULL");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Track.Id trackId = resultData.getParcelable(ImportServiceResultReceiver.RESULT_EXTRA_TRACK_ID);
|
||||||
String fileName = resultData.getString(ImportServiceResultReceiver.RESULT_EXTRA_FILENAME);
|
String fileName = resultData.getString(ImportServiceResultReceiver.RESULT_EXTRA_FILENAME);
|
||||||
String message = resultData.getString(ImportServiceResultReceiver.RESULT_EXTRA_MESSAGE);
|
String message = resultData.getString(ImportServiceResultReceiver.RESULT_EXTRA_MESSAGE);
|
||||||
|
|
||||||
@@ -74,6 +76,7 @@ public class ImportViewModel extends AndroidViewModel implements ImportServiceRe
|
|||||||
summary.fileErrors.add(getApplication().getString(R.string.import_error_info, fileName, message));
|
summary.fileErrors.add(getApplication().getString(R.string.import_error_info, fileName, message));
|
||||||
break;
|
break;
|
||||||
case ImportServiceResultReceiver.RESULT_CODE_IMPORTED:
|
case ImportServiceResultReceiver.RESULT_CODE_IMPORTED:
|
||||||
|
summary.importedTrackIds.add(trackId);
|
||||||
summary.successCount++;
|
summary.successCount++;
|
||||||
break;
|
break;
|
||||||
case ImportServiceResultReceiver.RESULT_CODE_ALREADY_EXISTS:
|
case ImportServiceResultReceiver.RESULT_CODE_ALREADY_EXISTS:
|
||||||
@@ -92,6 +95,7 @@ public class ImportViewModel extends AndroidViewModel implements ImportServiceRe
|
|||||||
private int successCount;
|
private int successCount;
|
||||||
private int existsCount;
|
private int existsCount;
|
||||||
private int errorCount;
|
private int errorCount;
|
||||||
|
private ArrayList<Track.Id> importedTrackIds = new ArrayList<>();
|
||||||
private final ArrayList<String> fileErrors = new ArrayList<>();
|
private final ArrayList<String> fileErrors = new ArrayList<>();
|
||||||
|
|
||||||
public int getTotalCount() {
|
public int getTotalCount() {
|
||||||
@@ -110,6 +114,10 @@ public class ImportViewModel extends AndroidViewModel implements ImportServiceRe
|
|||||||
return errorCount;
|
return errorCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ArrayList<Track.Id> getImportedTrackIds() {
|
||||||
|
return importedTrackIds;
|
||||||
|
}
|
||||||
|
|
||||||
public ArrayList<String> getFileErrors() {
|
public ArrayList<String> getFileErrors() {
|
||||||
return fileErrors;
|
return fileErrors;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,12 +63,13 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
|||||||
|
|
||||||
private boolean trackStarted = false;
|
private boolean trackStarted = false;
|
||||||
private String extendedDataType;
|
private String extendedDataType;
|
||||||
private ArrayList<TrackPoint> trackPoints = new ArrayList<>();
|
private final ArrayList<TrackPoint> trackPoints = new ArrayList<>();
|
||||||
private ArrayList<Float> speedList = new ArrayList<>();
|
private final ArrayList<Float> speedList = new ArrayList<>();
|
||||||
private ArrayList<Float> cadenceList = new ArrayList<>();
|
private final ArrayList<Float> cadenceList = new ArrayList<>();
|
||||||
private ArrayList<Float> heartRateList = new ArrayList<>();
|
private final ArrayList<Float> heartRateList = new ArrayList<>();
|
||||||
private ArrayList<Float> powerList = new ArrayList<>();
|
private final ArrayList<Float> powerList = new ArrayList<>();
|
||||||
private ArrayList<Float> elevationGainList = new ArrayList<>();
|
private final ArrayList<Float> elevationGainList = new ArrayList<>();
|
||||||
|
private final ArrayList<Float> elevationLossList = new ArrayList<>();
|
||||||
|
|
||||||
public KmlFileTrackImporter(Context context) {
|
public KmlFileTrackImporter(Context context) {
|
||||||
this(context, new ContentProviderUtils(context));
|
this(context, new ContentProviderUtils(context));
|
||||||
@@ -217,6 +218,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
|||||||
cadenceList.clear();
|
cadenceList.clear();
|
||||||
powerList.clear();
|
powerList.clear();
|
||||||
elevationGainList.clear();
|
elevationGainList.clear();
|
||||||
|
elevationLossList.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -242,6 +244,9 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
|||||||
if (i < elevationGainList.size()) {
|
if (i < elevationGainList.size()) {
|
||||||
trackPoint.setElevationGain(elevationGainList.get(i));
|
trackPoint.setElevationGain(elevationGainList.get(i));
|
||||||
}
|
}
|
||||||
|
if (i < elevationLossList.size()) {
|
||||||
|
trackPoint.setElevationLoss(elevationLossList.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
insertTrackPoint(trackPoint);
|
insertTrackPoint(trackPoint);
|
||||||
}
|
}
|
||||||
@@ -311,6 +316,9 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
|||||||
case KmlTrackWriter.EXTENDED_DATA_TYPE_ELEVATION_GAIN:
|
case KmlTrackWriter.EXTENDED_DATA_TYPE_ELEVATION_GAIN:
|
||||||
elevationGainList.add(value);
|
elevationGainList.add(value);
|
||||||
break;
|
break;
|
||||||
|
case KmlTrackWriter.EXTENDED_DATA_TYPE_ELEVATION_LOSS:
|
||||||
|
elevationLossList.add(value);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
Log.w(TAG, "Data from extended data " + extendedDataType + " is not (yet) supported.");
|
Log.w(TAG, "Data from extended data " + extendedDataType + " is not (yet) supported.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,7 +153,6 @@ public class KmzTrackImporter implements TrackImporter {
|
|||||||
* TODO: May load multiple tracks, but only returns the last Track.Id.
|
* TODO: May load multiple tracks, but only returns the last Track.Id.
|
||||||
*
|
*
|
||||||
* @param inputStream kmz input stream.
|
* @param inputStream kmz input stream.
|
||||||
* @return null if error or the id of the track otherwise.
|
|
||||||
*/
|
*/
|
||||||
private Track.Id findAndParseKmlFile(InputStream inputStream) {
|
private Track.Id findAndParseKmlFile(InputStream inputStream) {
|
||||||
try (ZipInputStream zipInputStream = new ZipInputStream(inputStream)) {
|
try (ZipInputStream zipInputStream = new ZipInputStream(inputStream)) {
|
||||||
@@ -171,12 +170,16 @@ public class KmzTrackImporter implements TrackImporter {
|
|||||||
trackId = parseKml(zipInputStream);
|
trackId = parseKml(zipInputStream);
|
||||||
if (trackId == null) {
|
if (trackId == null) {
|
||||||
Log.d(TAG, "Unable to parse kml in kmz");
|
Log.d(TAG, "Unable to parse kml in kmz");
|
||||||
throw new RuntimeException(context.getString(R.string.import_unable_to_import_file, fileName));
|
throw new ImportParserException(context.getString(R.string.import_unable_to_import_file, fileName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
zipInputStream.closeEntry();
|
zipInputStream.closeEntry();
|
||||||
}
|
}
|
||||||
|
if (trackId == null) {
|
||||||
|
Log.d(TAG, "Unable to find doc.kml in kmz");
|
||||||
|
throw new ImportParserException(context.getString(R.string.import_no_kml_file_found));
|
||||||
|
}
|
||||||
return trackId;
|
return trackId;
|
||||||
} catch (ImportParserException | ImportAlreadyExistsException e) {
|
} catch (ImportParserException | ImportAlreadyExistsException e) {
|
||||||
Log.e(TAG, "Unable to import file", e);
|
Log.e(TAG, "Unable to import file", e);
|
||||||
@@ -194,7 +197,6 @@ public class KmzTrackImporter implements TrackImporter {
|
|||||||
* @param trackId the id of the Track.
|
* @param trackId the id of the Track.
|
||||||
*/
|
*/
|
||||||
private void deleteOrphanImages(Context context, Track.Id trackId) {
|
private void deleteOrphanImages(Context context, Track.Id trackId) {
|
||||||
if (!trackId.isValid()) {
|
|
||||||
// 1.- Gets all photo names in the markers of the track identified by id.
|
// 1.- Gets all photo names in the markers of the track identified by id.
|
||||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||||
List<Marker> markers = contentProviderUtils.getMarkers(trackId);
|
List<Marker> markers = contentProviderUtils.getMarkers(trackId);
|
||||||
@@ -219,7 +221,6 @@ public class KmzTrackImporter implements TrackImporter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cleans up import.
|
* Cleans up import.
|
||||||
@@ -271,6 +272,7 @@ public class KmzTrackImporter implements TrackImporter {
|
|||||||
* Reads an image file (zipInputStream) and save it in a file called fileName inside photo folder.
|
* Reads an image file (zipInputStream) and save it in a file called fileName inside photo folder.
|
||||||
*
|
*
|
||||||
* @param zipInputStream the zip input stream
|
* @param zipInputStream the zip input stream
|
||||||
|
* @param trackId the track's id which image belongs to.
|
||||||
* @param fileName the file name
|
* @param fileName the file name
|
||||||
*/
|
*/
|
||||||
private void readAndSaveImageFile(ZipInputStream zipInputStream, Track.Id trackId, String fileName) throws IOException {
|
private void readAndSaveImageFile(ZipInputStream zipInputStream, Track.Id trackId, String fileName) throws IOException {
|
||||||
|
|||||||
@@ -352,7 +352,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
|||||||
|
|
||||||
trackStatisticsUpdater = new TrackStatisticsUpdater(track.getTrackStatistics().getStartTime_ms());
|
trackStatisticsUpdater = new TrackStatisticsUpdater(track.getTrackStatistics().getStartTime_ms());
|
||||||
|
|
||||||
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false)) {
|
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null)) {
|
||||||
trackStatisticsUpdater.addTrackPoint(trackPointIterator, recordingDistanceInterval);
|
trackStatisticsUpdater.addTrackPoint(trackPointIterator, recordingDistanceInterval);
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
Log.e(TAG, "RuntimeException", e);
|
Log.e(TAG, "RuntimeException", e);
|
||||||
@@ -665,6 +665,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
|||||||
try {
|
try {
|
||||||
if (elevationSumManager != null) {
|
if (elevationSumManager != null) {
|
||||||
trackPoint.setElevationGain(elevationSumManager.getElevationGain_m());
|
trackPoint.setElevationGain(elevationSumManager.getElevationGain_m());
|
||||||
|
trackPoint.setElevationLoss(elevationSumManager.getElevationLoss_m());
|
||||||
elevationSumManager.reset();
|
elevationSumManager.reset();
|
||||||
}
|
}
|
||||||
contentProviderUtils.insertTrackPoint(trackPoint, track.getId());
|
contentProviderUtils.insertTrackPoint(trackPoint, track.getId());
|
||||||
@@ -680,7 +681,6 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
|||||||
voiceExecutor.update();
|
voiceExecutor.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the recording track time.
|
* Updates the recording track time.
|
||||||
*
|
*
|
||||||
@@ -718,6 +718,17 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
|||||||
return elevationSumManager.getElevationGain_m();
|
return elevationSumManager.getElevationGain_m();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the relative elevation loss (since last trackpoint).
|
||||||
|
*/
|
||||||
|
Float getElevationLoss_m() {
|
||||||
|
if (elevationSumManager == null || !elevationSumManager.isConnected()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return elevationSumManager.getElevationLoss_m();
|
||||||
|
}
|
||||||
|
|
||||||
private void showNotification(boolean isGpsStarted) {
|
private void showNotification(boolean isGpsStarted) {
|
||||||
if (isRecording()) {
|
if (isRecording()) {
|
||||||
Intent intent = IntentUtils.newIntent(this, TrackRecordingActivity.class)
|
Intent intent = IntentUtils.newIntent(this, TrackRecordingActivity.class)
|
||||||
|
|||||||
@@ -104,6 +104,11 @@ class TrackRecordingServiceBinder extends android.os.Binder implements TrackReco
|
|||||||
return trackRecordingService.getElevationGain_m();
|
return trackRecordingService.getElevationGain_m();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Float getElevationLoss_m() {
|
||||||
|
return trackRecordingService.getElevationLoss_m();
|
||||||
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@Override
|
@Override
|
||||||
public void setRemoteSensorManager(BluetoothRemoteSensorManager remoteSensorManager) {
|
public void setRemoteSensorManager(BluetoothRemoteSensorManager remoteSensorManager) {
|
||||||
|
|||||||
@@ -78,6 +78,13 @@ public interface TrackRecordingServiceInterface {
|
|||||||
*/
|
*/
|
||||||
Float getElevationGain_m();
|
Float getElevationGain_m();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current elevation loss.
|
||||||
|
*
|
||||||
|
* @return elevation loss in m or null.
|
||||||
|
*/
|
||||||
|
Float getElevationLoss_m();
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
void setRemoteSensorManager(BluetoothRemoteSensorManager remoteSensorManager);
|
void setRemoteSensorManager(BluetoothRemoteSensorManager remoteSensorManager);
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class GpsStatus {
|
|||||||
|
|
||||||
private GpsStatusValue gpsStatus = GpsStatusValue.GPS_NONE;
|
private GpsStatusValue gpsStatus = GpsStatusValue.GPS_NONE;
|
||||||
private GpsStatusListener client;
|
private GpsStatusListener client;
|
||||||
private Context context;
|
private final Context context;
|
||||||
|
|
||||||
// Last location. It can be null.
|
// Last location. It can be null.
|
||||||
private Location lastLocation = null;
|
private Location lastLocation = null;
|
||||||
@@ -51,7 +51,7 @@ class GpsStatus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Handler gpsStatusHandler;
|
private final Handler gpsStatusHandler;
|
||||||
private GpsStatusRunner gpsStatusRunner = null;
|
private GpsStatusRunner gpsStatusRunner = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import de.dennisguse.opentracks.content.data.TrackPoint;
|
|||||||
|
|
||||||
public class HandlerServer {
|
public class HandlerServer {
|
||||||
|
|
||||||
private static String TAG = HandlerServer.class.getSimpleName();
|
private static final String TAG = HandlerServer.class.getSimpleName();
|
||||||
|
|
||||||
private final LocationHandler locationHandler;
|
private final LocationHandler locationHandler;
|
||||||
private final HandlerServerInterface service;
|
private final HandlerServerInterface service;
|
||||||
|
|||||||
@@ -112,6 +112,6 @@ public class ElevationSumManager implements SensorEventListener {
|
|||||||
|
|
||||||
lastSeenSensorValue_hPa = value_hPa;
|
lastSeenSensorValue_hPa = value_hPa;
|
||||||
|
|
||||||
Log.v(TAG, "elevation gain: " + elevationGain_m);
|
Log.v(TAG, "elevation gain: " + elevationGain_m + ", elevation loss: " + elevationLoss_m);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,8 +168,7 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
|||||||
String category = track != null ? track.getCategory() : "";
|
String category = track != null ? track.getCategory() : "";
|
||||||
|
|
||||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(track.getId());
|
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(track.getId());
|
||||||
IntervalStatistics intervalStatistics = new IntervalStatistics();
|
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, (float) (PreferencesUtils.isMetricUnits(context) ? 1d * UnitConversions.KM_TO_M : 1d * UnitConversions.MI_TO_M));
|
||||||
intervalStatistics.build(trackPoints, (float) (PreferencesUtils.isMetricUnits(context) ? 1d * UnitConversions.KM_TO_M : 1d * UnitConversions.MI_TO_M));
|
|
||||||
IntervalStatistics.Interval lastInterval = intervalStatistics.getLastInterval();
|
IntervalStatistics.Interval lastInterval = intervalStatistics.getLastInterval();
|
||||||
|
|
||||||
String announcement = AnnouncementUtils.getAnnouncement(context, trackStatistics, category, lastInterval);
|
String announcement = AnnouncementUtils.getAnnouncement(context, trackStatistics, category, lastInterval);
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import android.content.Intent;
|
|||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.documentfile.provider.DocumentFile;
|
import androidx.documentfile.provider.DocumentFile;
|
||||||
import androidx.fragment.app.DialogFragment;
|
import androidx.fragment.app.DialogFragment;
|
||||||
@@ -18,6 +18,7 @@ import androidx.preference.PreferenceFragmentCompat;
|
|||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.AbstractActivity;
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||||
@@ -31,7 +32,7 @@ import de.dennisguse.opentracks.util.HackUtils;
|
|||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.util.StringUtils;
|
import de.dennisguse.opentracks.util.StringUtils;
|
||||||
|
|
||||||
public class SettingsActivity extends AppCompatActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller, ResetDialogPreference.ResetCallback {
|
public class SettingsActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller, ResetDialogPreference.ResetCallback {
|
||||||
|
|
||||||
private static final String TAG = SettingsActivity.class.getSimpleName();
|
private static final String TAG = SettingsActivity.class.getSimpleName();
|
||||||
public static final String EXTRAS_CHECK_EXPORT_DIRECTORY = "Check Export Directory";
|
public static final String EXTRAS_CHECK_EXPORT_DIRECTORY = "Check Export Directory";
|
||||||
@@ -43,12 +44,6 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
|
|||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
setContentView(R.layout.settings);
|
|
||||||
|
|
||||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
|
||||||
toolbar.setTitle(R.string.menu_settings);
|
|
||||||
setSupportActionBar(toolbar);
|
|
||||||
|
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
if (intent != null && intent.hasExtra(EXTRAS_CHECK_EXPORT_DIRECTORY)) {
|
if (intent != null && intent.hasExtra(EXTRAS_CHECK_EXPORT_DIRECTORY)) {
|
||||||
checkExportDirectory = true;
|
checkExportDirectory = true;
|
||||||
@@ -56,6 +51,17 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
|
|||||||
onReset();
|
onReset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected View getRootView() {
|
||||||
|
return getLayoutInflater().inflate(R.layout.settings, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setupActionBarBack(Toolbar toolbar) {
|
||||||
|
super.setupActionBarBack(toolbar);
|
||||||
|
toolbar.setTitle(R.string.menu_settings);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
if (checkExportDirectory) {
|
if (checkExportDirectory) {
|
||||||
@@ -154,7 +160,7 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
|
|||||||
activityPreferenceDialog = ActivityTypePreference.ActivityPreferenceDialog.newInstance(preference.getKey());
|
activityPreferenceDialog = ActivityTypePreference.ActivityPreferenceDialog.newInstance(preference.getKey());
|
||||||
dialogFragment = activityPreferenceDialog;
|
dialogFragment = activityPreferenceDialog;
|
||||||
} else if (preference instanceof BluetoothLeHeartRatePreference) {
|
} else if (preference instanceof BluetoothLeHeartRatePreference) {
|
||||||
dialogFragment = BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(preference.getKey(), BluetoothUtils.HEART_RATE_SERVICE_UUID);
|
dialogFragment = BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(preference.getKey(), BluetoothUtils.HEART_RATE_SUPPORTING_DEVICES);
|
||||||
} else if (preference instanceof BluetoothLeCyclingCadenceAndSpeedPreference) {
|
} else if (preference instanceof BluetoothLeCyclingCadenceAndSpeedPreference) {
|
||||||
dialogFragment = BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(preference.getKey(), BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID);
|
dialogFragment = BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(preference.getKey(), BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID);
|
||||||
} else if (preference instanceof BluetoothLeCyclingPowerPreference) {
|
} else if (preference instanceof BluetoothLeCyclingPowerPreference) {
|
||||||
|
|||||||
+30
-16
@@ -8,8 +8,6 @@ import android.bluetooth.le.ScanResult;
|
|||||||
import android.bluetooth.le.ScanSettings;
|
import android.bluetooth.le.ScanSettings;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.graphics.drawable.Animatable;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.ParcelUuid;
|
import android.os.ParcelUuid;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
@@ -18,13 +16,15 @@ import android.util.Log;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.preference.DialogPreference;
|
import androidx.preference.DialogPreference;
|
||||||
import androidx.preference.PreferenceDialogFragmentCompat;
|
import androidx.preference.PreferenceDialogFragmentCompat;
|
||||||
|
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.settings.BluetoothLeAdapter;
|
import de.dennisguse.opentracks.settings.BluetoothLeAdapter;
|
||||||
@@ -39,7 +39,7 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
|
|||||||
|
|
||||||
private static final String TAG = BluetoothLeSensorPreference.class.getSimpleName();
|
private static final String TAG = BluetoothLeSensorPreference.class.getSimpleName();
|
||||||
|
|
||||||
private static final String ARG_BLUETOOTH_UUID = "bluetoothUUID";
|
private static final String ARG_BLE_SERVICE_UUIDS = "bluetoothUUID";
|
||||||
|
|
||||||
private static final int DEVICE_NONE_RESOURCEID = R.string.value_none;
|
private static final int DEVICE_NONE_RESOURCEID = R.string.value_none;
|
||||||
|
|
||||||
@@ -94,6 +94,8 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
|
|||||||
|
|
||||||
public static class BluetoothLeSensorPreferenceDialog extends PreferenceDialogFragmentCompat {
|
public static class BluetoothLeSensorPreferenceDialog extends PreferenceDialogFragmentCompat {
|
||||||
|
|
||||||
|
private AnimatedVectorDrawableCompat bluetoothIcon;
|
||||||
|
|
||||||
private int selectedEntryIndex;
|
private int selectedEntryIndex;
|
||||||
private final BluetoothLeAdapter listAdapter = new BluetoothLeAdapter();
|
private final BluetoothLeAdapter listAdapter = new BluetoothLeAdapter();
|
||||||
|
|
||||||
@@ -109,8 +111,9 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBatchScanResults(List<ScanResult> results) {
|
public void onBatchScanResults(List<ScanResult> results) {
|
||||||
for (ScanResult scanResult : results) {
|
for (ScanResult result : results) {
|
||||||
listAdapter.add(scanResult.getDevice());
|
Log.d(TAG, "Found device " + result.getDevice().getName() + " " + result);
|
||||||
|
listAdapter.add(result.getDevice());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,10 +127,14 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public static BluetoothLeSensorPreferenceDialog newInstance(String preferenceKey, UUID sensorUUID) {
|
public static BluetoothLeSensorPreferenceDialog newInstance(String preferenceKey, UUID sensorUUID) {
|
||||||
|
return newInstance(preferenceKey, Collections.singletonList(sensorUUID));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BluetoothLeSensorPreferenceDialog newInstance(String preferenceKey, List<UUID> sensorUUIDs) {
|
||||||
final BluetoothLeSensorPreferenceDialog fragment = new BluetoothLeSensorPreferenceDialog();
|
final BluetoothLeSensorPreferenceDialog fragment = new BluetoothLeSensorPreferenceDialog();
|
||||||
final Bundle b = new Bundle(1);
|
final Bundle b = new Bundle(1);
|
||||||
b.putString(ARG_KEY, preferenceKey);
|
b.putString(ARG_KEY, preferenceKey);
|
||||||
b.putSerializable(ARG_BLUETOOTH_UUID, sensorUUID);
|
b.putParcelableArrayList(ARG_BLE_SERVICE_UUIDS, new ArrayList<>(sensorUUIDs.stream().map(ParcelUuid::new).collect(Collectors.toList())));
|
||||||
fragment.setArguments(b);
|
fragment.setArguments(b);
|
||||||
return fragment;
|
return fragment;
|
||||||
}
|
}
|
||||||
@@ -136,8 +143,12 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
|
|||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
UUID sensorUUID = (UUID) getArguments().getSerializable(ARG_BLUETOOTH_UUID);
|
List<ParcelUuid> serviceUUIDs = getArguments().getParcelableArrayList(ARG_BLE_SERVICE_UUIDS);
|
||||||
Log.i(TAG, "UUID: " + sensorUUID);
|
|
||||||
|
// Don't know why: need to load the drawable _twice_, so that animation is actually started.
|
||||||
|
bluetoothIcon = AnimatedVectorDrawableCompat.create(getContext(), R.drawable.ic_bluetooth_searching_animated_24dp);
|
||||||
|
bluetoothIcon = AnimatedVectorDrawableCompat.create(getContext(), R.drawable.ic_bluetooth_searching_animated_24dp);
|
||||||
|
bluetoothIcon.start();
|
||||||
|
|
||||||
BluetoothAdapter bluetoothAdapter = BluetoothUtils.getAdapter(getContext());
|
BluetoothAdapter bluetoothAdapter = BluetoothUtils.getAdapter(getContext());
|
||||||
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
|
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
|
||||||
@@ -148,7 +159,7 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (bluetoothAdapter.isDiscovering()) {
|
if (bluetoothAdapter.isDiscovering()) {
|
||||||
Log.i(TAG, "Cancelling ongoing B<aluetooth discovery.");
|
Log.i(TAG, "Cancelling ongoing Bluetooth discovery.");
|
||||||
bluetoothAdapter.cancelDiscovery();
|
bluetoothAdapter.cancelDiscovery();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,9 +181,7 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
|
|||||||
selectedEntryIndex = 1;
|
selectedEntryIndex = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
ScanFilter.Builder scanFilterBuilder = new ScanFilter.Builder().setServiceUuid(new ParcelUuid(sensorUUID));
|
List<ScanFilter> scanFilter = serviceUUIDs.stream().map(it -> new ScanFilter.Builder().setServiceUuid(it).build()).collect(Collectors.toList());
|
||||||
List<ScanFilter> scanFilter = new ArrayList<>();
|
|
||||||
scanFilter.add(scanFilterBuilder.build());
|
|
||||||
|
|
||||||
ScanSettings.Builder scanSettingsBuilder = new ScanSettings.Builder();
|
ScanSettings.Builder scanSettingsBuilder = new ScanSettings.Builder();
|
||||||
scanSettingsBuilder.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY);
|
scanSettingsBuilder.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY);
|
||||||
@@ -193,9 +202,7 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
|
|||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
});
|
});
|
||||||
|
|
||||||
Drawable icon = ContextCompat.getDrawable(getContext(), R.drawable.ic_bluetooth_searching_animated_24dp);
|
builder.setIcon(bluetoothIcon);
|
||||||
((Animatable) icon).start();
|
|
||||||
builder.setIcon(icon);
|
|
||||||
|
|
||||||
builder.setPositiveButton(null, null);
|
builder.setPositiveButton(null, null);
|
||||||
}
|
}
|
||||||
@@ -214,5 +221,12 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
bluetoothIcon = null;
|
||||||
|
scanner = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ public class TrackStatistics {
|
|||||||
private double maxSpeed_mps;
|
private double maxSpeed_mps;
|
||||||
// The total elevation gained (meters).
|
// The total elevation gained (meters).
|
||||||
private Float totalElevationGain_m = null;
|
private Float totalElevationGain_m = null;
|
||||||
|
// The total elevation lost (meters).
|
||||||
|
private Float totalElevationLoss_m = null;
|
||||||
|
|
||||||
public TrackStatistics() {
|
public TrackStatistics() {
|
||||||
}
|
}
|
||||||
@@ -63,6 +65,7 @@ public class TrackStatistics {
|
|||||||
maxSpeed_mps = other.maxSpeed_mps;
|
maxSpeed_mps = other.maxSpeed_mps;
|
||||||
elevationExtremities.set(other.elevationExtremities.getMin(), other.elevationExtremities.getMax());
|
elevationExtremities.set(other.elevationExtremities.getMin(), other.elevationExtremities.getMax());
|
||||||
totalElevationGain_m = other.totalElevationGain_m;
|
totalElevationGain_m = other.totalElevationGain_m;
|
||||||
|
totalElevationLoss_m = other.totalElevationLoss_m;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -91,6 +94,15 @@ public class TrackStatistics {
|
|||||||
totalElevationGain_m += other.totalElevationGain_m;
|
totalElevationGain_m += other.totalElevationGain_m;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (totalElevationLoss_m == null) {
|
||||||
|
if (other.totalElevationLoss_m != null) {
|
||||||
|
totalElevationLoss_m = other.totalElevationLoss_m;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (other.totalElevationLoss_m != null) {
|
||||||
|
totalElevationLoss_m += other.totalElevationLoss_m;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -257,6 +269,26 @@ public class TrackStatistics {
|
|||||||
totalElevationGain_m += gain_m;
|
totalElevationGain_m += gain_m;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasTotalElevationLoss() {
|
||||||
|
return totalElevationLoss_m != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable
|
||||||
|
Float getTotalElevationLoss() {
|
||||||
|
return totalElevationLoss_m;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTotalElevationLoss(float totalElevationLoss_m) {
|
||||||
|
this.totalElevationLoss_m = totalElevationLoss_m;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addTotalElevationLoss(float loss_m) {
|
||||||
|
if (totalElevationLoss_m == null) {
|
||||||
|
totalElevationLoss_m = 0f;
|
||||||
|
}
|
||||||
|
totalElevationLoss_m += loss_m;
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
@@ -264,6 +296,7 @@ public class TrackStatistics {
|
|||||||
+ "; Total Distance: " + getTotalDistance() + "; Total Time: " + getTotalTime()
|
+ "; Total Distance: " + getTotalDistance() + "; Total Time: " + getTotalTime()
|
||||||
+ "; Moving Time: " + getMovingTime() + "; Max Speed: " + getMaxSpeed()
|
+ "; Moving Time: " + getMovingTime() + "; Max Speed: " + getMaxSpeed()
|
||||||
+ "; Min Elevation: " + getMinElevation() + "; Max Elevation: " + getMaxElevation()
|
+ "; Min Elevation: " + getMinElevation() + "; Max Elevation: " + getMaxElevation()
|
||||||
+ "; Elevation Gain: " + getTotalElevationGain() + "}";
|
+ "; Elevation Gain: " + getTotalElevationGain()
|
||||||
|
+ "; Elevation Loss: " + getTotalElevationLoss() + "}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,6 +145,12 @@ public class TrackStatisticsUpdater {
|
|||||||
Log.d(TAG, "elevation gain: " + trackPoint.getElevationGain());
|
Log.d(TAG, "elevation gain: " + trackPoint.getElevationGain());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Get elevation loss
|
||||||
|
if (trackPoint.hasElevationLoss()) {
|
||||||
|
currentSegment.addTotalElevationLoss(trackPoint.getElevationLoss());
|
||||||
|
Log.d(TAG, "elevation loss: " + trackPoint.getElevationLoss());
|
||||||
|
}
|
||||||
|
|
||||||
if (lastTrackPoint == null || lastMovingTrackPoint == null) {
|
if (lastTrackPoint == null || lastMovingTrackPoint == null) {
|
||||||
lastTrackPoint = trackPoint;
|
lastTrackPoint = trackPoint;
|
||||||
lastMovingTrackPoint = trackPoint;
|
lastMovingTrackPoint = trackPoint;
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ import android.util.Log;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
|
import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
|
||||||
@@ -39,6 +42,12 @@ public class BluetoothUtils {
|
|||||||
public static final UUID HEART_RATE_SERVICE_UUID = new UUID(0x180D00001000L, 0x800000805f9b34fbL);
|
public static final UUID HEART_RATE_SERVICE_UUID = new UUID(0x180D00001000L, 0x800000805f9b34fbL);
|
||||||
public static final UUID HEART_RATE_MEASUREMENT_CHAR_UUID = new UUID(0x2A3700001000L, 0x800000805f9b34fbL);
|
public static final UUID HEART_RATE_MEASUREMENT_CHAR_UUID = new UUID(0x2A3700001000L, 0x800000805f9b34fbL);
|
||||||
|
|
||||||
|
public static final List<UUID> HEART_RATE_SUPPORTING_DEVICES = Collections.unmodifiableList(Arrays.asList(
|
||||||
|
BluetoothUtils.HEART_RATE_SERVICE_UUID,
|
||||||
|
//Devices that support HEART_RATE_SERVICE_UUID, but do not announce HEART_RATE_SERVICE_UUID in there BLE announcement messages (during device discovery).
|
||||||
|
UUID.fromString("0000fee0-0000-1000-8000-00805f9b34fb") //Miband3
|
||||||
|
));
|
||||||
|
|
||||||
public static final UUID CYCLING_POWER_UUID = new UUID(0x181800001000L, 0x800000805f9b34fbL);
|
public static final UUID CYCLING_POWER_UUID = new UUID(0x181800001000L, 0x800000805f9b34fbL);
|
||||||
public static final UUID CYCLING_POWER_MEASUREMENT_CHAR_UUID = new UUID(0x2A6300001000L, 0x800000805f9b34fbL);
|
public static final UUID CYCLING_POWER_MEASUREMENT_CHAR_UUID = new UUID(0x2A6300001000L, 0x800000805f9b34fbL);
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import android.provider.MediaStore;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.content.FileProvider;
|
import androidx.core.content.FileProvider;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -80,7 +81,6 @@ public class IntentUtils {
|
|||||||
trackDescription = track == null ? "" : new DescriptionGenerator(context).generateTrackDescription(track, false);
|
trackDescription = track == null ? "" : new DescriptionGenerator(context).generateTrackDescription(track, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
String action = trackIds.length == 1 ? Intent.ACTION_SEND : Intent.ACTION_SEND_MULTIPLE;
|
|
||||||
String mime = "";
|
String mime = "";
|
||||||
|
|
||||||
ArrayList<Uri> uris = new ArrayList<>();
|
ArrayList<Uri> uris = new ArrayList<>();
|
||||||
@@ -95,9 +95,8 @@ public class IntentUtils {
|
|||||||
uris.add(uriAndMime.first);
|
uris.add(uriAndMime.first);
|
||||||
mime = uriAndMime.second;
|
mime = uriAndMime.second;
|
||||||
}
|
}
|
||||||
return new Intent(action)
|
return new Intent(Intent.ACTION_SEND_MULTIPLE)
|
||||||
.setType(mime)
|
.setType(mime)
|
||||||
.setAction(Intent.ACTION_SEND_MULTIPLE)
|
|
||||||
.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris)
|
.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris)
|
||||||
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
.putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.share_track_subject))
|
.putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.share_track_subject))
|
||||||
@@ -105,13 +104,41 @@ public class IntentUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an intent to share an image with an app.
|
* Creates an intent to share a track file with an app.
|
||||||
*
|
*
|
||||||
* @param context the context.
|
* @param context the context
|
||||||
* @param uri uri with the image to share.
|
* @param markerIds the marker ids
|
||||||
|
* @return an Intent or null (if nothing can be shared).
|
||||||
*/
|
*/
|
||||||
public static Intent newShareImageIntent(Context context, Uri uri) {
|
@Nullable
|
||||||
String mime = context.getContentResolver().getType(uri);
|
public static Intent newShareFileIntent(Context context, Marker.Id... markerIds) {
|
||||||
|
if (markerIds.length == 0) {
|
||||||
|
throw new RuntimeException("Need to share at least one marker.");
|
||||||
|
}
|
||||||
|
|
||||||
|
String mime = null;
|
||||||
|
|
||||||
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||||
|
ArrayList<Uri> uris = new ArrayList<>();
|
||||||
|
for (Marker.Id markerId : markerIds) {
|
||||||
|
Marker marker = contentProviderUtils.getMarker(markerId);
|
||||||
|
if (marker == null) {
|
||||||
|
Log.e(TAG, "MarkerId " + markerId.getId() + " could not be resolved.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (marker.getPhotoURI() == null) {
|
||||||
|
Log.e(TAG, "MarkerId " + markerId.getId() + " has no picture.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
mime = context.getContentResolver().getType(marker.getPhotoURI());
|
||||||
|
|
||||||
|
uris.add(marker.getPhotoURI());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uris.size() == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Because the #166 bug, when you import KMZ tracks then it creates file:/// from markers with photo.
|
* Because the #166 bug, when you import KMZ tracks then it creates file:/// from markers with photo.
|
||||||
@@ -119,11 +146,10 @@ public class IntentUtils {
|
|||||||
* In .setType, to avoid side effects because the #166 bug described above it checks if mime is null.
|
* In .setType, to avoid side effects because the #166 bug described above it checks if mime is null.
|
||||||
* If it is then it hardcode "images/*".
|
* If it is then it hardcode "images/*".
|
||||||
*/
|
*/
|
||||||
return new Intent(Intent.ACTION_SEND)
|
return new Intent(Intent.ACTION_SEND_MULTIPLE)
|
||||||
.setType(mime != null ? mime : "image/*")
|
.setType(mime != null ? mime : "image/*")
|
||||||
.setAction(Intent.ACTION_SEND)
|
.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris)
|
||||||
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
.putExtra(Intent.EXTRA_STREAM, uri)
|
|
||||||
.putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.share_image_subject))
|
.putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.share_image_subject))
|
||||||
.putExtra(Intent.EXTRA_TEXT, context.getString(R.string.share_image_body));
|
.putExtra(Intent.EXTRA_TEXT, context.getString(R.string.share_image_body));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,10 +16,9 @@
|
|||||||
|
|
||||||
package de.dennisguse.opentracks.util;
|
package de.dennisguse.opentracks.util;
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.TypedArray;
|
import android.graphics.Bitmap;
|
||||||
import android.net.Uri;
|
import android.graphics.BitmapFactory;
|
||||||
import android.text.format.DateUtils;
|
import android.text.format.DateUtils;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -37,15 +36,13 @@ import de.dennisguse.opentracks.R;
|
|||||||
*/
|
*/
|
||||||
public class ListItemUtils {
|
public class ListItemUtils {
|
||||||
|
|
||||||
private static final int LIST_PREFERRED_ITEM_HEIGHT_DEFAULT = 128;
|
|
||||||
|
|
||||||
private ListItemUtils() {
|
private ListItemUtils() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets a list item.
|
* Sets a list item.
|
||||||
*
|
*
|
||||||
* @param activity the activity
|
* @param context the context
|
||||||
* @param view the list item view
|
* @param view the list item view
|
||||||
* @param isRecording true if recording
|
* @param isRecording true if recording
|
||||||
* @param isPaused true if paused
|
* @param isPaused true if paused
|
||||||
@@ -59,23 +56,9 @@ public class ListItemUtils {
|
|||||||
* @param useRelativeTime true to display relative time if appropriate
|
* @param useRelativeTime true to display relative time if appropriate
|
||||||
* @param category the category value
|
* @param category the category value
|
||||||
* @param description the description value
|
* @param description the description value
|
||||||
* @param photoUrl the photo url
|
* @param hasPhoto true if this list item has photo
|
||||||
*/
|
*/
|
||||||
public static void setListItem(Activity activity, View view, boolean isRecording, boolean isPaused, int iconId, int iconContentDescriptionId, String name, String totalTime, String totalDistance, int markerCount, long startTime, boolean useRelativeTime, String category, String description, String photoUrl) {
|
public static void setListItem(Context context, View view, boolean isRecording, boolean isPaused, int iconId, int iconContentDescriptionId, String name, String totalTime, String totalDistance, int markerCount, long startTime, boolean useRelativeTime, String category, String description, boolean hasPhoto) {
|
||||||
// Set photo
|
|
||||||
ImageView photo = view.findViewById(R.id.list_item_photo);
|
|
||||||
ImageView textGradient = view.findViewById(R.id.list_item_text_gradient);
|
|
||||||
boolean hasPhoto = photoUrl != null && !photoUrl.equals("");
|
|
||||||
|
|
||||||
photo.setVisibility(hasPhoto ? View.VISIBLE : View.GONE);
|
|
||||||
textGradient.setVisibility(hasPhoto ? View.VISIBLE : View.GONE);
|
|
||||||
|
|
||||||
if (hasPhoto) {
|
|
||||||
photo.getLayoutParams().height = getPhotoHeight(activity);
|
|
||||||
photo.setImageResource(android.R.color.transparent);
|
|
||||||
photo.setImageURI(Uri.parse(photoUrl));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set icon
|
// Set icon
|
||||||
if (isRecording) {
|
if (isRecording) {
|
||||||
iconId = isPaused ? R.drawable.ic_track_paused : R.drawable.ic_track_recording;
|
iconId = isPaused ? R.drawable.ic_track_paused : R.drawable.ic_track_recording;
|
||||||
@@ -84,27 +67,27 @@ public class ListItemUtils {
|
|||||||
|
|
||||||
ImageView iconImageView = view.findViewById(R.id.list_item_icon);
|
ImageView iconImageView = view.findViewById(R.id.list_item_icon);
|
||||||
iconImageView.setImageResource(iconId);
|
iconImageView.setImageResource(iconId);
|
||||||
iconImageView.setContentDescription(activity.getString(iconContentDescriptionId));
|
iconImageView.setContentDescription(context.getString(iconContentDescriptionId));
|
||||||
|
|
||||||
// Set name
|
// Set name
|
||||||
TextView nameTextView = view.findViewById(R.id.list_item_name);
|
TextView nameTextView = view.findViewById(R.id.list_item_name);
|
||||||
setTextView(activity, nameTextView, name, hasPhoto);
|
setTextView(context, nameTextView, name, hasPhoto);
|
||||||
|
|
||||||
// Set totalTime/totalDistance
|
// Set totalTime/totalDistance
|
||||||
TextView timeDistanceTextView = view.findViewById(R.id.list_item_time_distance);
|
TextView timeDistanceTextView = view.findViewById(R.id.list_item_time_distance);
|
||||||
String timeDistanceText;
|
String timeDistanceText;
|
||||||
if (isRecording) {
|
if (isRecording) {
|
||||||
timeDistanceTextView.setTextColor(activity.getResources().getColor(isPaused ? android.R.color.white : R.color.recording_text));
|
timeDistanceTextView.setTextColor(context.getResources().getColor(isPaused ? android.R.color.white : R.color.recording_text));
|
||||||
timeDistanceText = activity.getString(isPaused ? R.string.generic_paused : R.string.generic_recording);
|
timeDistanceText = context.getString(isPaused ? R.string.generic_paused : R.string.generic_recording);
|
||||||
} else {
|
} else {
|
||||||
// Match list_item_time_distance in list_item.xml
|
// Match list_item_time_distance in list_item.xml
|
||||||
timeDistanceTextView.setTextAppearance(activity, R.style.TextSmall);
|
timeDistanceTextView.setTextAppearance(context, R.style.TextSmall);
|
||||||
timeDistanceText = getTimeDistance(totalTime, totalDistance);
|
timeDistanceText = getTimeDistance(totalTime, totalDistance);
|
||||||
if (markerCount > 0) {
|
if (markerCount > 0) {
|
||||||
timeDistanceText += " \u2027";
|
timeDistanceText += " \u2027";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setTextView(activity, timeDistanceTextView, timeDistanceText, hasPhoto);
|
setTextView(context, timeDistanceTextView, timeDistanceText, hasPhoto);
|
||||||
|
|
||||||
// Set markerCount
|
// Set markerCount
|
||||||
ImageView markerCountIcon = view.findViewById(R.id.list_item_marker_count_icon);
|
ImageView markerCountIcon = view.findViewById(R.id.list_item_marker_count_icon);
|
||||||
@@ -119,15 +102,15 @@ public class ListItemUtils {
|
|||||||
layoutParams.width = lineHeight;
|
layoutParams.width = lineHeight;
|
||||||
layoutParams.height = lineHeight;
|
layoutParams.height = lineHeight;
|
||||||
}
|
}
|
||||||
setTextView(activity, markerCountTextView, markerCountValue, hasPhoto);
|
setTextView(context, markerCountTextView, markerCountValue, hasPhoto);
|
||||||
|
|
||||||
// Set date/time
|
// Set date/time
|
||||||
String[] dateTime = getDateTime(isRecording, activity, startTime, useRelativeTime);
|
String[] dateTime = getDateTime(isRecording, context, startTime, useRelativeTime);
|
||||||
TextView dateTextView = view.findViewById(R.id.list_item_date);
|
TextView dateTextView = view.findViewById(R.id.list_item_date);
|
||||||
setTextView(activity, dateTextView, dateTime[0], hasPhoto);
|
setTextView(context, dateTextView, dateTime[0], hasPhoto);
|
||||||
|
|
||||||
TextView timeTextView = view.findViewById(R.id.list_item_time);
|
TextView timeTextView = view.findViewById(R.id.list_item_time);
|
||||||
setTextView(activity, timeTextView, dateTime[1], hasPhoto);
|
setTextView(context, timeTextView, dateTime[1], hasPhoto);
|
||||||
|
|
||||||
// Set category and description
|
// Set category and description
|
||||||
TextView categoryDescriptionTextView = view.findViewById(R.id.list_item_category_description);
|
TextView categoryDescriptionTextView = view.findViewById(R.id.list_item_category_description);
|
||||||
@@ -135,15 +118,15 @@ public class ListItemUtils {
|
|||||||
|
|
||||||
// Place categoryDescription in either ownerTimeDistanceTextView or categoryDescriptionTextView
|
// Place categoryDescription in either ownerTimeDistanceTextView or categoryDescriptionTextView
|
||||||
if (timeDistanceTextView.getVisibility() == View.GONE && markerCountIcon.getVisibility() == View.GONE) {
|
if (timeDistanceTextView.getVisibility() == View.GONE && markerCountIcon.getVisibility() == View.GONE) {
|
||||||
setTextView(activity, categoryDescriptionTextView, null, hasPhoto);
|
setTextView(context, categoryDescriptionTextView, null, hasPhoto);
|
||||||
// Match list_item_category_description in list_item.xml
|
// Match list_item_category_description in list_item.xml
|
||||||
timeDistanceTextView.setSingleLine(false);
|
timeDistanceTextView.setSingleLine(false);
|
||||||
timeDistanceTextView.setMaxLines(2);
|
timeDistanceTextView.setMaxLines(2);
|
||||||
setTextView(activity, timeDistanceTextView, categoryDescription, hasPhoto);
|
setTextView(context, timeDistanceTextView, categoryDescription, hasPhoto);
|
||||||
} else {
|
} else {
|
||||||
// Match list_item_time_distance in list_item.xml
|
// Match list_item_time_distance in list_item.xml
|
||||||
timeDistanceTextView.setSingleLine(true);
|
timeDistanceTextView.setSingleLine(true);
|
||||||
setTextView(activity, categoryDescriptionTextView, categoryDescription, hasPhoto);
|
setTextView(context, categoryDescriptionTextView, categoryDescription, hasPhoto);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adjust iconImageView layout gravity
|
// Adjust iconImageView layout gravity
|
||||||
@@ -151,6 +134,41 @@ public class ListItemUtils {
|
|||||||
params.gravity = timeDistanceTextView.getVisibility() == View.GONE && markerCountIcon.getVisibility() == View.GONE ? Gravity.TOP : Gravity.CENTER_VERTICAL;
|
params.gravity = timeDistanceTextView.getVisibility() == View.GONE && markerCountIcon.getVisibility() == View.GONE ? Gravity.TOP : Gravity.CENTER_VERTICAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Bitmap decodeSampledBitmapFromResource(String photoUrl, int reqWidth, int reqHeight) {
|
||||||
|
// First decode with inJustDecodeBounds=true to check dimensions
|
||||||
|
final BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
|
options.inJustDecodeBounds = true;
|
||||||
|
BitmapFactory.decodeFile(photoUrl, options);
|
||||||
|
|
||||||
|
// Calculate inSampleSize
|
||||||
|
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
|
||||||
|
|
||||||
|
// Decode bitmap with inSampleSize set
|
||||||
|
options.inJustDecodeBounds = false;
|
||||||
|
return BitmapFactory.decodeFile(photoUrl, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
|
||||||
|
// Raw height and width of image
|
||||||
|
final int height = options.outHeight;
|
||||||
|
final int width = options.outWidth;
|
||||||
|
int inSampleSize = 1;
|
||||||
|
|
||||||
|
if (height > reqHeight || width > reqWidth) {
|
||||||
|
|
||||||
|
final int halfHeight = height / 2;
|
||||||
|
final int halfWidth = width / 2;
|
||||||
|
|
||||||
|
// Calculate the largest inSampleSize value that is a power of 2 and keeps both height and width larger than the requested height and width.
|
||||||
|
while ((halfHeight / inSampleSize) >= reqHeight
|
||||||
|
&& (halfWidth / inSampleSize) >= reqWidth) {
|
||||||
|
inSampleSize *= 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return inSampleSize;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a string for share owner, total time, and total distance.
|
* Gets a string for share owner, total time, and total distance.
|
||||||
*
|
*
|
||||||
@@ -220,17 +238,4 @@ public class ListItemUtils {
|
|||||||
textView.setShadowLayer(0, 0, 0, 0);
|
textView.setShadowLayer(0, 0, 0, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the photo height.
|
|
||||||
*
|
|
||||||
* @param context the context
|
|
||||||
*/
|
|
||||||
private static int getPhotoHeight(Context context) {
|
|
||||||
int[] attrs = new int[]{android.R.attr.listPreferredItemHeight};
|
|
||||||
TypedArray typeArray = context.obtainStyledAttributes(attrs);
|
|
||||||
int height = typeArray.getDimensionPixelSize(0, LIST_PREFERRED_ITEM_HEIGHT_DEFAULT);
|
|
||||||
typeArray.recycle();
|
|
||||||
return 2 * height;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.content.Context;
|
|||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
|
|
||||||
@@ -15,6 +16,6 @@ public class MarkerUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Drawable getDefaultPhoto(@NonNull Context context) {
|
public static Drawable getDefaultPhoto(@NonNull Context context) {
|
||||||
return context.getResources().getDrawable(ICON_ID);
|
return ContextCompat.getDrawable(context, ICON_ID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,18 +5,18 @@ import android.hardware.SensorManager;
|
|||||||
public class PressureSensorUtils {
|
public class PressureSensorUtils {
|
||||||
|
|
||||||
//Everything above is considered a meaningful change in elevation.
|
//Everything above is considered a meaningful change in elevation.
|
||||||
private static float ELEVATION_CHANGE_DIFF_M = 3.0f;
|
private static final float ELEVATION_CHANGE_DIFF_M = 3.0f;
|
||||||
|
|
||||||
private static float EXPONENTIAL_SMOOTHING = 0.3f;
|
private static final float EXPONENTIAL_SMOOTHING = 0.3f;
|
||||||
|
|
||||||
private PressureSensorUtils() {
|
private PressureSensorUtils() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ElevationChange {
|
public static class ElevationChange {
|
||||||
|
|
||||||
private float currentSensorValue_hPa;
|
private final float currentSensorValue_hPa;
|
||||||
|
|
||||||
private float elevationChange_m;
|
private final float elevationChange_m;
|
||||||
|
|
||||||
public ElevationChange(float currentSensorValue_hPa, float elevationChange_m) {
|
public ElevationChange(float currentSensorValue_hPa, float elevationChange_m) {
|
||||||
this.currentSensorValue_hPa = currentSensorValue_hPa;
|
this.currentSensorValue_hPa = currentSensorValue_hPa;
|
||||||
@@ -36,7 +36,7 @@ public class PressureSensorUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public float getElevationLoss_m() {
|
public float getElevationLoss_m() {
|
||||||
return elevationChange_m < 0 ? elevationChange_m : 0;
|
return elevationChange_m < 0 ? -1 * elevationChange_m : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ public class PressureSensorUtils {
|
|||||||
|
|
||||||
// Limit elevation change by ELEVATION_CHANGE_DIFF and computes pressure value accordingly.
|
// Limit elevation change by ELEVATION_CHANGE_DIFF and computes pressure value accordingly.
|
||||||
ElevationChange elevationChange = new ElevationChange(currentSensorValue_hPa, elevationChange_m);
|
ElevationChange elevationChange = new ElevationChange(currentSensorValue_hPa, elevationChange_m);
|
||||||
if (elevationChange.getElevationGain_m() > 0) {
|
if (elevationChange.getElevationChange_m() > 0) {
|
||||||
return new ElevationChange(getBarometricPressure(SensorManager.PRESSURE_STANDARD_ATMOSPHERE, lastSensorValue_m + ELEVATION_CHANGE_DIFF_M), ELEVATION_CHANGE_DIFF_M);
|
return new ElevationChange(getBarometricPressure(SensorManager.PRESSURE_STANDARD_ATMOSPHERE, lastSensorValue_m + ELEVATION_CHANGE_DIFF_M), ELEVATION_CHANGE_DIFF_M);
|
||||||
} else {
|
} else {
|
||||||
return new ElevationChange(getBarometricPressure(SensorManager.PRESSURE_STANDARD_ATMOSPHERE, lastSensorValue_m - ELEVATION_CHANGE_DIFF_M), -1 * ELEVATION_CHANGE_DIFF_M);
|
return new ElevationChange(getBarometricPressure(SensorManager.PRESSURE_STANDARD_ATMOSPHERE, lastSensorValue_m - ELEVATION_CHANGE_DIFF_M), -1 * ELEVATION_CHANGE_DIFF_M);
|
||||||
|
|||||||
@@ -118,6 +118,34 @@ public class StringUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String formatSpeed(Context context, double speed_mps, boolean metricUnits, boolean reportSpeed) {
|
||||||
|
if (Double.isNaN(speed_mps) || Double.isInfinite(speed_mps)) {
|
||||||
|
speed_mps = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double speed = speed_mps * UnitConversions.M_TO_KM; //KM p sec
|
||||||
|
if (!metricUnits) {
|
||||||
|
speed *= UnitConversions.KM_TO_MI;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reportSpeed) {
|
||||||
|
if (metricUnits) {
|
||||||
|
return context.getString(R.string.value_float_kilometer_hour, speed * UnitConversions.S_TO_HR);
|
||||||
|
} else {
|
||||||
|
return context.getString(R.string.value_float_mile_hour, speed * UnitConversions.S_TO_HR);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int pace = speed == 0 ? 0 : (int) Math.round(1 / speed); //sec / [KM | MI]
|
||||||
|
int minutes = pace / 60;
|
||||||
|
int seconds = pace % 60;
|
||||||
|
if (metricUnits) {
|
||||||
|
return context.getString(R.string.value_pace_kilometer, minutes, seconds);
|
||||||
|
} else {
|
||||||
|
return context.getString(R.string.value_pace_mile, minutes, seconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static String formatDecimal(double value) {
|
private static String formatDecimal(double value) {
|
||||||
return StringUtils.formatDecimal(value, 2);
|
return StringUtils.formatDecimal(value, 2);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,13 @@ package de.dennisguse.opentracks.util;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
|
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.R;
|
||||||
|
|
||||||
public class ThemeUtils {
|
public class ThemeUtils {
|
||||||
|
|
||||||
private ThemeUtils() {
|
private ThemeUtils() {
|
||||||
@@ -37,4 +40,18 @@ public class ThemeUtils {
|
|||||||
|
|
||||||
return ContextCompat.getColor(context, typedValue.resourceId);
|
return ContextCompat.getColor(context, typedValue.resourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int getFontSizeSmallInPx(Context context) {
|
||||||
|
TypedArray typedArray = context.obtainStyledAttributes(R.style.TextSmall, new int[]{android.R.attr.textSize});
|
||||||
|
int fontSize = typedArray.getDimensionPixelSize(0, 12);
|
||||||
|
typedArray.recycle();
|
||||||
|
return fontSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getFontSizeMediumInPx(Context context) {
|
||||||
|
TypedArray typedArray = context.obtainStyledAttributes(R.style.TextMedium, new int[]{android.R.attr.textSize});
|
||||||
|
int fontSize = typedArray.getDimensionPixelSize(0, 15);
|
||||||
|
typedArray.recycle();
|
||||||
|
return fontSize;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
package de.dennisguse.opentracks.viewmodels;
|
package de.dennisguse.opentracks.viewmodels;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -10,50 +14,58 @@ import de.dennisguse.opentracks.stats.TrackStatistics;
|
|||||||
|
|
||||||
public class AggregatedStatistics {
|
public class AggregatedStatistics {
|
||||||
|
|
||||||
private final Map<String, AggregatedStatistic> aggregatedStatistics = new HashMap<>();
|
private final Map<String, AggregatedStatistic> dataMap = new HashMap<>();
|
||||||
|
|
||||||
public void aggregate(List<Track> tracks) {
|
private final List<AggregatedStatistic> dataList = new ArrayList<>();
|
||||||
|
|
||||||
|
public AggregatedStatistics(@NonNull List<Track> tracks) {
|
||||||
for (Track track : tracks) {
|
for (Track track : tracks) {
|
||||||
aggregate(track);
|
aggregate(track);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dataList.addAll(dataMap.values());
|
||||||
|
Collections.sort(dataList, (o1, o2) -> {
|
||||||
|
if (o1.getCountTracks() == o2.getCountTracks()) {
|
||||||
|
return o1.getCategory().compareTo(o2.getCategory());
|
||||||
|
}
|
||||||
|
return (o1.getCountTracks() < o2.getCountTracks() ? 1 : -1);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void aggregate(Track track) {
|
@VisibleForTesting
|
||||||
|
public void aggregate(@NonNull Track track) {
|
||||||
String category = track.getCategory();
|
String category = track.getCategory();
|
||||||
if (aggregatedStatistics.containsKey(category)) {
|
if (dataMap.containsKey(category)) {
|
||||||
aggregatedStatistics.get(category).add(track.getTrackStatistics());
|
dataMap.get(category).add(track.getTrackStatistics());
|
||||||
} else {
|
} else {
|
||||||
aggregatedStatistics.put(category, new AggregatedStatistic(track.getTrackStatistics()));
|
dataMap.put(category, new AggregatedStatistic(category, track.getTrackStatistics()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getCount() {
|
public int getCount() {
|
||||||
return aggregatedStatistics.size();
|
return dataMap.size();
|
||||||
}
|
|
||||||
|
|
||||||
public String getSportName(int position) {
|
|
||||||
return getKey(position);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public AggregatedStatistic get(String category) {
|
public AggregatedStatistic get(String category) {
|
||||||
return aggregatedStatistics.get(category);
|
return dataMap.get(category);
|
||||||
}
|
|
||||||
|
|
||||||
private String getKey(int position) {
|
|
||||||
return (new ArrayList<>(aggregatedStatistics.keySet())).get(position);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public AggregatedStatistic getItem(int position) {
|
public AggregatedStatistic getItem(int position) {
|
||||||
return (new ArrayList<>(aggregatedStatistics.values())).get(position);
|
return dataList.get(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class AggregatedStatistic {
|
public static class AggregatedStatistic {
|
||||||
|
private final String category;
|
||||||
private final TrackStatistics trackStatistics;
|
private final TrackStatistics trackStatistics;
|
||||||
private int countTracks;
|
private int countTracks = 1;
|
||||||
|
|
||||||
public AggregatedStatistic(TrackStatistics trackStatistics) {
|
public AggregatedStatistic(String category, TrackStatistics trackStatistics) {
|
||||||
|
this.category = category;
|
||||||
this.trackStatistics = trackStatistics;
|
this.trackStatistics = trackStatistics;
|
||||||
this.countTracks = 1;
|
}
|
||||||
|
|
||||||
|
public String getCategory() {
|
||||||
|
return category;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TrackStatistics getTrackStatistics() {
|
public TrackStatistics getTrackStatistics() {
|
||||||
|
|||||||
@@ -33,8 +33,7 @@ public class AggregatedStatisticsModel extends AndroidViewModel {
|
|||||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(getApplication().getApplicationContext());
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(getApplication().getApplicationContext());
|
||||||
List<Track> tracks = contentProviderUtils.getTracks();
|
List<Track> tracks = contentProviderUtils.getTracks();
|
||||||
|
|
||||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics();
|
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics(tracks);
|
||||||
aggregatedStatistics.aggregate(tracks);
|
|
||||||
|
|
||||||
aggregatedStats.postValue(aggregatedStatistics);
|
aggregatedStats.postValue(aggregatedStatistics);
|
||||||
}).start();
|
}).start();
|
||||||
|
|||||||
@@ -8,31 +8,33 @@ import de.dennisguse.opentracks.util.LocationUtils;
|
|||||||
import de.dennisguse.opentracks.util.UnitConversions;
|
import de.dennisguse.opentracks.util.UnitConversions;
|
||||||
|
|
||||||
public class IntervalStatistics {
|
public class IntervalStatistics {
|
||||||
private List<Interval> intervalList = new ArrayList<>();
|
private final List<Interval> intervalList = new ArrayList<>();
|
||||||
private float distanceInterval_m;
|
private final float distanceInterval_m;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param trackPointList the list of TrackPoint.
|
* @param trackPoints the list of TrackPoint.
|
||||||
* @param distanceInterval_m the meters of every interval.
|
* @param distanceInterval_m the meters of every interval.
|
||||||
*/
|
*/
|
||||||
public void build(List<TrackPoint> trackPointList, float distanceInterval_m) {
|
public IntervalStatistics(List<TrackPoint> trackPoints, float distanceInterval_m) {
|
||||||
intervalList.clear();
|
intervalList.clear();
|
||||||
this.distanceInterval_m = distanceInterval_m;
|
this.distanceInterval_m = distanceInterval_m;
|
||||||
|
|
||||||
if (trackPointList == null || trackPointList.size() == 0) {
|
if (trackPoints == null || trackPoints.size() == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Interval interval = new Interval();
|
Interval interval = new Interval();
|
||||||
interval.gain_m += trackPointList.get(0).hasElevationGain() ? trackPointList.get(0).getElevationGain() : 0;
|
interval.gain_m += trackPoints.get(0).hasElevationGain() ? trackPoints.get(0).getElevationGain() : 0;
|
||||||
for (int i = 1; i < trackPointList.size(); i++) {
|
interval.loss_m += trackPoints.get(0).hasElevationLoss() ? trackPoints.get(0).getElevationLoss() : 0;
|
||||||
TrackPoint prevTrackPoint = trackPointList.get(i - 1);
|
for (int i = 1; i < trackPoints.size(); i++) {
|
||||||
TrackPoint trackPoint = trackPointList.get(i);
|
TrackPoint prevTrackPoint = trackPoints.get(i - 1);
|
||||||
|
TrackPoint trackPoint = trackPoints.get(i);
|
||||||
|
|
||||||
if (LocationUtils.isValidLocation(trackPoint.getLocation()) && LocationUtils.isValidLocation(prevTrackPoint.getLocation())) {
|
if (LocationUtils.isValidLocation(trackPoint.getLocation()) && LocationUtils.isValidLocation(prevTrackPoint.getLocation())) {
|
||||||
interval.distance_m += prevTrackPoint.distanceTo(trackPoint);
|
interval.distance_m += prevTrackPoint.distanceTo(trackPoint);
|
||||||
interval.time_ms += trackPoint.getTime() - prevTrackPoint.getTime();
|
interval.time_ms += trackPoint.getTime() - prevTrackPoint.getTime();
|
||||||
interval.gain_m += trackPoint.hasElevationGain() ? trackPoint.getElevationGain() : 0;
|
interval.gain_m += trackPoint.hasElevationGain() ? trackPoint.getElevationGain() : 0;
|
||||||
|
interval.loss_m += trackPoint.hasElevationLoss() ? trackPoint.getElevationLoss() : 0;
|
||||||
|
|
||||||
if (interval.distance_m >= distanceInterval_m) {
|
if (interval.distance_m >= distanceInterval_m) {
|
||||||
float adjustFactor = distanceInterval_m / interval.distance_m;
|
float adjustFactor = distanceInterval_m / interval.distance_m;
|
||||||
@@ -51,6 +53,7 @@ public class IntervalStatistics {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public List<Interval> getIntervalList() {
|
public List<Interval> getIntervalList() {
|
||||||
return intervalList;
|
return intervalList;
|
||||||
}
|
}
|
||||||
@@ -62,10 +65,6 @@ public class IntervalStatistics {
|
|||||||
* @return the interval object or null if any interval is completed.
|
* @return the interval object or null if any interval is completed.
|
||||||
*/
|
*/
|
||||||
public Interval getLastInterval() {
|
public Interval getLastInterval() {
|
||||||
if (intervalList == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (intervalList.size() == 1 && intervalList.get(0).getDistance_m() < distanceInterval_m) {
|
if (intervalList.size() == 1 && intervalList.get(0).getDistance_m() < distanceInterval_m) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -83,6 +82,7 @@ public class IntervalStatistics {
|
|||||||
private float distance_m = 0f;
|
private float distance_m = 0f;
|
||||||
private float time_ms = 0f;
|
private float time_ms = 0f;
|
||||||
private float gain_m = 0f;
|
private float gain_m = 0f;
|
||||||
|
private float loss_m = 0f;
|
||||||
|
|
||||||
public Interval() {}
|
public Interval() {}
|
||||||
|
|
||||||
@@ -90,12 +90,14 @@ public class IntervalStatistics {
|
|||||||
this.distance_m = distance_m;
|
this.distance_m = distance_m;
|
||||||
this.time_ms = time_ms;
|
this.time_ms = time_ms;
|
||||||
this.gain_m = 0f;
|
this.gain_m = 0f;
|
||||||
|
this.loss_m = 0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Interval(Interval i) {
|
public Interval(Interval i) {
|
||||||
distance_m = i.distance_m;
|
distance_m = i.distance_m;
|
||||||
time_ms = i.time_ms;
|
time_ms = i.time_ms;
|
||||||
gain_m = i.gain_m;
|
gain_m = i.gain_m;
|
||||||
|
loss_m = i.loss_m;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float getDistance_m() {
|
public float getDistance_m() {
|
||||||
@@ -120,5 +122,9 @@ public class IntervalStatistics {
|
|||||||
public float getGain_m() {
|
public float getGain_m() {
|
||||||
return gain_m;
|
return gain_m;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public float getLoss_m() {
|
||||||
|
return loss_m;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import de.dennisguse.opentracks.util.UnitConversions;
|
|||||||
*/
|
*/
|
||||||
public class IntervalStatisticsModel {
|
public class IntervalStatisticsModel {
|
||||||
|
|
||||||
private IntervalStatistics intervalStats;
|
|
||||||
private final List<TrackPoint> trackPoints = new ArrayList<>();
|
private final List<TrackPoint> trackPoints = new ArrayList<>();
|
||||||
|
|
||||||
public IntervalStatistics getIntervalStats(boolean metricUnits, @Nullable IntervalOption interval) {
|
public IntervalStatistics getIntervalStats(boolean metricUnits, @Nullable IntervalOption interval) {
|
||||||
@@ -23,10 +22,8 @@ public class IntervalStatisticsModel {
|
|||||||
interval = IntervalOption.OPTION_1;
|
interval = IntervalOption.OPTION_1;
|
||||||
}
|
}
|
||||||
|
|
||||||
intervalStats = new IntervalStatistics();
|
|
||||||
float distanceInterval = metricUnits ? (float) (interval.getValue() * UnitConversions.KM_TO_M) : (float) (interval.getValue() * UnitConversions.MI_TO_M);
|
float distanceInterval = metricUnits ? (float) (interval.getValue() * UnitConversions.KM_TO_M) : (float) (interval.getValue() * UnitConversions.MI_TO_M);
|
||||||
intervalStats.build(trackPoints, distanceInterval);
|
return new IntervalStatistics(trackPoints, distanceInterval);
|
||||||
return intervalStats;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,176 @@
|
|||||||
|
package de.dennisguse.opentracks.viewmodels;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.ParcelFileDescriptor;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.lifecycle.AndroidViewModel;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileDescriptor;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.R;
|
||||||
|
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.services.TrackRecordingServiceConnection;
|
||||||
|
import de.dennisguse.opentracks.util.FileUtils;
|
||||||
|
|
||||||
|
public class MarkerEditViewModel extends AndroidViewModel {
|
||||||
|
|
||||||
|
private static final String TAG = MarkerEditViewModel.class.getSimpleName();
|
||||||
|
|
||||||
|
private MutableLiveData<Marker> markerData;
|
||||||
|
private boolean isNewMarker;
|
||||||
|
private Uri photoOriginalUri;
|
||||||
|
private final TrackRecordingServiceConnection trackRecordingServiceConnection = new TrackRecordingServiceConnection();
|
||||||
|
|
||||||
|
public MarkerEditViewModel(@NonNull Application application) {
|
||||||
|
super(application);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Marker> getMarkerData(@NonNull Track.Id trackId, @Nullable Marker.Id markerId) {
|
||||||
|
if (markerData == null) {
|
||||||
|
markerData = new MutableLiveData<>();
|
||||||
|
trackRecordingServiceConnection.startConnection(getApplication());
|
||||||
|
loadData(trackId, markerId);
|
||||||
|
}
|
||||||
|
return markerData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCleared() {
|
||||||
|
super.onCleared();
|
||||||
|
trackRecordingServiceConnection.unbind(getApplication());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadData(Track.Id trackId, Marker.Id markerId) {
|
||||||
|
Marker marker;
|
||||||
|
isNewMarker = markerId == null;
|
||||||
|
if (isNewMarker) {
|
||||||
|
int nextMarkerNumber = trackId == null ? 0 : new ContentProviderUtils(getApplication()).getNextMarkerNumber(trackId);
|
||||||
|
marker = new Marker(trackId);
|
||||||
|
marker.setName(getApplication().getString(R.string.marker_name_format, nextMarkerNumber));
|
||||||
|
} else {
|
||||||
|
marker = new ContentProviderUtils(getApplication()).getMarker(markerId);
|
||||||
|
if (marker.hasPhoto()) {
|
||||||
|
photoOriginalUri = marker.getPhotoURI();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
markerData.postValue(marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
private @NonNull Marker getMarker() throws NoSuchElementException {
|
||||||
|
Marker marker = markerData != null ? markerData.getValue() : null;
|
||||||
|
if (marker == null) {
|
||||||
|
Log.d(TAG, "Marker data shouldn't be null. Call getMarkerData before.");
|
||||||
|
throw new NoSuchElementException("Marker data shouldn't be null. Call getMarkerData before.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return marker;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deletePhoto(@Nullable Uri photoUri) {
|
||||||
|
if (photoUri == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
File photoFile = FileUtils.getPhotoFileIfExists(getApplication(), markerData.getValue().getTrackId(), photoUri);
|
||||||
|
if (photoFile != null) {
|
||||||
|
FileUtils.deleteDirectoryRecurse(photoFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deletePhoto(Marker marker) {
|
||||||
|
if (marker.hasPhoto()) {
|
||||||
|
deletePhoto(marker.getPhotoURI());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onPhotoDelete(String name, String category, String description) {
|
||||||
|
Marker marker = getMarker();
|
||||||
|
if (marker.hasPhoto()) {
|
||||||
|
if (!marker.getPhotoURI().equals(photoOriginalUri)) {
|
||||||
|
deletePhoto(marker.getPhotoURI());
|
||||||
|
}
|
||||||
|
marker.setPhotoUrl(null);
|
||||||
|
marker.setName(name);
|
||||||
|
marker.setCategory(category);
|
||||||
|
marker.setDescription(description);
|
||||||
|
markerData.postValue(marker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onNewCameraPhoto(@NonNull Uri photoUri, String name, String category, String description) {
|
||||||
|
Marker marker = getMarker();
|
||||||
|
marker.setPhotoUrl(photoUri.toString());
|
||||||
|
marker.setName(name);
|
||||||
|
marker.setCategory(category);
|
||||||
|
marker.setDescription(description);
|
||||||
|
markerData.postValue(marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onNewGalleryPhoto(@NonNull Uri srcUri, String name, String category, String description) {
|
||||||
|
Marker marker = getMarker();
|
||||||
|
|
||||||
|
try (ParcelFileDescriptor parcelFd = getApplication().getContentResolver().openFileDescriptor(srcUri, "r")) {
|
||||||
|
FileDescriptor srcFd = parcelFd.getFileDescriptor();
|
||||||
|
File dstFile = new File(FileUtils.getImageUrl(getApplication(), marker.getTrackId()));
|
||||||
|
FileUtils.copy(srcFd, dstFile);
|
||||||
|
|
||||||
|
Uri photoUri = FileUtils.getUriForFile(getApplication(), dstFile);
|
||||||
|
marker.setPhotoUrl(photoUri.toString());
|
||||||
|
marker.setName(name);
|
||||||
|
marker.setCategory(category);
|
||||||
|
marker.setDescription(description);
|
||||||
|
markerData.postValue(marker);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, e.getMessage());
|
||||||
|
Toast.makeText(getApplication(), R.string.marker_add_canceled, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onAddDone(@NonNull Marker marker, String name, String category, String description) {
|
||||||
|
trackRecordingServiceConnection.addMarker(getApplication(), name, category, description, marker.hasPhoto() ? marker.getPhotoURI().toString() : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSaveDone(@NonNull Marker marker, String name, String category, String description) {
|
||||||
|
marker.setName(name);
|
||||||
|
marker.setCategory(category);
|
||||||
|
marker.setDescription(description);
|
||||||
|
new ContentProviderUtils(getApplication()).updateMarker(getApplication(), marker);
|
||||||
|
|
||||||
|
if (photoOriginalUri != null && (!marker.hasPhoto() || !photoOriginalUri.equals(marker.getPhotoURI()))) {
|
||||||
|
deletePhoto(photoOriginalUri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onDone(String name, String category, String description) {
|
||||||
|
Marker marker = getMarker();
|
||||||
|
if (isNewMarker) {
|
||||||
|
onAddDone(marker, name, category, description);
|
||||||
|
} else {
|
||||||
|
onSaveDone(marker, name, category, description);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onCancel() {
|
||||||
|
Marker marker = getMarker();
|
||||||
|
if (isNewMarker) {
|
||||||
|
// it's new marker -> clean all photos.
|
||||||
|
deletePhoto(marker);
|
||||||
|
deletePhoto(photoOriginalUri);
|
||||||
|
} else if (photoOriginalUri == null || (marker.hasPhoto() && !marker.getPhotoURI().equals(photoOriginalUri))) {
|
||||||
|
// it's an edit marker -> delete photo if it was empty or it was changed (leaving the original in that case).
|
||||||
|
deletePhoto(marker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M7,10l5,5 5,-5z"/>
|
||||||
|
</vector>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user