mirror of
https://codeberg.org/OpenTracksApp/OpenTracks.git
synced 2026-10-23 18:55:17 +02:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 597e1b2c7f | |||
| 8156993921 | |||
| 9890dc28bd | |||
| b65d83010d | |||
| 1a5d06f9c6 | |||
| 723c21d7ec | |||
| 8fe0c3be7d | |||
| e64792bf3f | |||
| 21bafff761 | |||
| c33c2de51d |
+2
-2
@@ -47,8 +47,8 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId 'de.dennisguse.opentracks'
|
||||
versionCode 6554
|
||||
versionName "v4.25.1"
|
||||
versionCode 6564
|
||||
versionName "v4.25.2"
|
||||
|
||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
v4.25.2: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Restored compatibility with OSMDashboard (Dashboard API protocol version 2)
|
||||
|
||||
Bugfix:
|
||||
- Update UI immediately after resuming a track
|
||||
|
||||
@@ -36,7 +36,8 @@ public class EspressoDeleteTrackTest {
|
||||
@Test
|
||||
public void espressoDeleteTrackTest() {
|
||||
// TrackListActivity: start recording
|
||||
onView(withId(R.id.track_list_fab_action)).perform(click());
|
||||
onView(withId(R.id.track_list_fab_action))
|
||||
.perform(click());
|
||||
|
||||
// TrackRecordingActivity
|
||||
onView(withId(R.id.track_recording_fab_action))
|
||||
@@ -46,10 +47,12 @@ public class EspressoDeleteTrackTest {
|
||||
.perform(longClick());
|
||||
|
||||
// TrackStoppedActivity
|
||||
onView(withId(R.id.finish_button)).perform(click());
|
||||
onView(withId(R.id.finish_button))
|
||||
.perform(click());
|
||||
|
||||
// select track
|
||||
onView(allOf(withParent(withId(R.id.track_list)), withParentIndex(0))).perform(longClick());
|
||||
onView(allOf(withParent(withId(R.id.track_list)), withParentIndex(0)))
|
||||
.perform(longClick());
|
||||
|
||||
// open menu and delete selected track
|
||||
onView(
|
||||
@@ -62,9 +65,11 @@ public class EspressoDeleteTrackTest {
|
||||
isDisplayed()))
|
||||
.perform(click());
|
||||
|
||||
onView(withText("Delete")).perform(click());
|
||||
onView(withText("Delete"))
|
||||
.perform(click());
|
||||
|
||||
onView(withText("OK")).perform(click());
|
||||
onView(withText("OK"))
|
||||
.perform(click());
|
||||
|
||||
// tracklist is empty now
|
||||
onView(allOf(withText("Start recording your next adventure here"), isDisplayed()));
|
||||
|
||||
@@ -15,7 +15,6 @@ import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static de.dennisguse.opentracks.util.EspressoUtils.waitFor;
|
||||
|
||||
import androidx.test.espresso.ViewInteraction;
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.LargeTest;
|
||||
@@ -45,41 +44,43 @@ public class EspressoEditTrackRecordingTest {
|
||||
public void espressoEditTrackRecordingTest() {
|
||||
{
|
||||
// TrackListActivity: start recording
|
||||
ViewInteraction fabRecordButton = onView(withId(R.id.track_list_fab_action));
|
||||
fabRecordButton.perform(click());
|
||||
onView(withId(R.id.track_list_fab_action))
|
||||
.perform(click());
|
||||
}
|
||||
{
|
||||
// TrackRecordingActivity
|
||||
ViewInteraction trackControllerStopButton = onView(withId(R.id.track_recording_fab_action));
|
||||
|
||||
// wait; stay recording
|
||||
trackControllerStopButton.perform(waitFor(4000));
|
||||
onView(withId(R.id.track_recording_fab_action))
|
||||
.perform(waitFor(4000));
|
||||
|
||||
// open menu
|
||||
openContextualActionModeOverflowMenu();
|
||||
|
||||
// Click the item.
|
||||
onView(withText(R.string.menu_edit)).perform(click());
|
||||
onView(withText(R.string.menu_edit))
|
||||
.perform(click());
|
||||
|
||||
// change name for "New Name"
|
||||
ViewInteraction textInputEditText = onView(withId(R.id.track_edit_name));
|
||||
textInputEditText.perform(scrollTo(), replaceText("New Name"));
|
||||
onView(withId(R.id.track_edit_name))
|
||||
.perform(scrollTo(), replaceText("New Name"));
|
||||
|
||||
ViewInteraction textInputEditText2 = onView(allOf(withId(R.id.track_edit_name), withText("New Name"), isDisplayed()));
|
||||
textInputEditText2.perform(closeSoftKeyboard());
|
||||
onView(allOf(withId(R.id.track_edit_name), withText("New Name"), isDisplayed()))
|
||||
.perform(closeSoftKeyboard());
|
||||
|
||||
// save edition
|
||||
ViewInteraction appCompatButton = onView(withId(R.id.track_edit_save));
|
||||
appCompatButton.perform(click());
|
||||
onView(withId(R.id.track_edit_save))
|
||||
.perform(click());
|
||||
|
||||
// stop;
|
||||
trackControllerStopButton.perform(longClick());
|
||||
onView(withId(R.id.track_recording_fab_action))
|
||||
.perform(longClick());
|
||||
|
||||
// it's on track stopped activity and there are two buttons
|
||||
ViewInteraction resumeButton = onView(withId(R.id.resume_button));
|
||||
resumeButton.check(matches(isDisplayed()));
|
||||
ViewInteraction finishButton = onView(withId(R.id.finish_button));
|
||||
finishButton.check(matches(isDisplayed()));
|
||||
onView(withId(R.id.resume_button))
|
||||
.check(matches(isDisplayed()));
|
||||
onView(withId(R.id.finish_button))
|
||||
.check(matches(isDisplayed()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ public class EspressoUITest {
|
||||
public void record_stop_resume_stop_finish() {
|
||||
{
|
||||
// TrackListActivity: start recording
|
||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.track_list_fab_action));
|
||||
trackControllerRecordButton.perform(click());
|
||||
onView(withId(R.id.track_list_fab_action))
|
||||
.perform(click());
|
||||
}
|
||||
{
|
||||
// TrackRecordingActivity: wait to record some time and then stop
|
||||
@@ -66,13 +66,12 @@ public class EspressoUITest {
|
||||
public void record_move_through_tabs() {
|
||||
{
|
||||
// TrackListActivity: start recording
|
||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.track_list_fab_action));
|
||||
trackControllerRecordButton.perform(click());
|
||||
onView(withId(R.id.track_list_fab_action))
|
||||
.perform(click());
|
||||
}
|
||||
{
|
||||
// TrackRecordingActivity
|
||||
ViewInteraction tabLayout = onView(withId(R.id.track_detail_activity_tablayout));
|
||||
ViewInteraction trackControllerStopButton = onView(withId(R.id.track_recording_fab_action));
|
||||
|
||||
tabLayout.perform(selectTabAtIndex(1));
|
||||
tabLayout.perform(waitFor(1000));
|
||||
@@ -87,7 +86,8 @@ public class EspressoUITest {
|
||||
tabLayout.perform(waitFor(1000));
|
||||
|
||||
// stop
|
||||
trackControllerStopButton.perform(longClick());
|
||||
onView(withId(R.id.track_recording_fab_action)).
|
||||
perform(longClick());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
package de.dennisguse.opentracks.publicapi;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class DataProviderTest {
|
||||
|
||||
private final ContentResolver resolver = ApplicationProvider.getApplicationContext().getContentResolver();
|
||||
|
||||
@Test
|
||||
public void supportsVersion2_tracks() {
|
||||
// given
|
||||
Uri uri = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/dashboard/tracks/1");
|
||||
String[] expectedTracksColumns = new String[]{
|
||||
"_id",
|
||||
"name",
|
||||
"description",
|
||||
"category",
|
||||
"activity_type_localized",
|
||||
"starttime",
|
||||
"stoptime",
|
||||
"totaltime",
|
||||
"movingtime",
|
||||
"totaldistance",
|
||||
"maxspeed",
|
||||
"avgspeed",
|
||||
"avgmovingspeed",
|
||||
"minelevation",
|
||||
"maxelevation",
|
||||
"elevationgain",
|
||||
"elevationloss"
|
||||
};
|
||||
|
||||
// when
|
||||
try (Cursor cursor = resolver.query(
|
||||
uri,
|
||||
expectedTracksColumns,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)) {
|
||||
// then
|
||||
assertEquals("vnd.android.cursor.dir/vnd.de.dennisguse.track", resolver.getType(uri));
|
||||
|
||||
assertNotNull(cursor);
|
||||
assertArrayEquals(expectedTracksColumns, cursor.getColumnNames());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void supportsVersion2_trackpoints() {
|
||||
// given
|
||||
Uri uri = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/dashboard/trackpoints/1");
|
||||
String[] expectedTrackpointsColumns = {
|
||||
"_id",
|
||||
"trackid",
|
||||
"latitude",
|
||||
"longitude",
|
||||
"time",
|
||||
"type",
|
||||
"speed"
|
||||
};
|
||||
|
||||
// when
|
||||
try (Cursor cursor = resolver.query(
|
||||
uri,
|
||||
expectedTrackpointsColumns,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)) {
|
||||
// then
|
||||
assertEquals("vnd.android.cursor.dir/vnd.de.dennisguse.trackpoint", resolver.getType(uri));
|
||||
|
||||
assertNotNull(cursor);
|
||||
assertArrayEquals(expectedTrackpointsColumns, cursor.getColumnNames());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void supportsVersion2_markers() {
|
||||
// given
|
||||
Uri uri = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/dashboard/markers/1");
|
||||
String[] expectedMarkersColumns = {
|
||||
"_id",
|
||||
"name",
|
||||
"description",
|
||||
"category",
|
||||
"trackid",
|
||||
"longitude",
|
||||
"latitude",
|
||||
"photoUrl",
|
||||
"icon"
|
||||
};
|
||||
|
||||
// when
|
||||
try (Cursor cursor = resolver.query(
|
||||
uri,
|
||||
expectedMarkersColumns,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)) {
|
||||
// then
|
||||
assertEquals("vnd.android.cursor.dir/vnd.de.dennisguse.waypoint", resolver.getType(uri));
|
||||
|
||||
assertNotNull(cursor);
|
||||
assertArrayEquals(expectedMarkersColumns, cursor.getColumnNames());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void supportsVersion3_tracks() {
|
||||
// given
|
||||
Uri uri = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/dashboard/tracks/1");
|
||||
String[] expectedTracksColumns = {
|
||||
"_id",
|
||||
"name",
|
||||
"description",
|
||||
"activity_type",
|
||||
"activity_type_localized",
|
||||
"time_start",
|
||||
"time_stop",
|
||||
"duration_total",
|
||||
"duration_moving",
|
||||
"distance",
|
||||
"speed_max",
|
||||
"altitude_min",
|
||||
"altitude_max",
|
||||
"altitude_gain",
|
||||
"altitude_loss"
|
||||
};
|
||||
|
||||
// when
|
||||
try (Cursor cursor = resolver.query(
|
||||
uri,
|
||||
expectedTracksColumns,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)) {
|
||||
// then
|
||||
assertEquals("vnd.android.cursor.dir/vnd.de.dennisguse.track", resolver.getType(uri));
|
||||
|
||||
assertNotNull(cursor);
|
||||
assertArrayEquals(expectedTracksColumns, cursor.getColumnNames());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void supportsVersion3_trackpoints() {
|
||||
//given
|
||||
Uri uri = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/dashboard/trackpoints/1");
|
||||
String[] expectedTrackpointsColumns = {
|
||||
"_id",
|
||||
"trackid",
|
||||
"latitude",
|
||||
"longitude",
|
||||
"time",
|
||||
"type",
|
||||
"speed"
|
||||
};
|
||||
|
||||
// when
|
||||
try (Cursor cursor = resolver.query(
|
||||
uri,
|
||||
expectedTrackpointsColumns,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)) {
|
||||
//then
|
||||
assertEquals("vnd.android.cursor.dir/vnd.de.dennisguse.trackpoint", resolver.getType(uri));
|
||||
|
||||
assertNotNull(cursor);
|
||||
assertArrayEquals(expectedTrackpointsColumns, cursor.getColumnNames());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void supportsVersion3_markers() {
|
||||
// given
|
||||
Uri uri = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/dashboard/markers/1");
|
||||
String[] expectedMarkersColumns = {
|
||||
"_id",
|
||||
"name",
|
||||
"description",
|
||||
"marker_type_localized",
|
||||
"trackid",
|
||||
"latitude",
|
||||
"longitude",
|
||||
"photoUrl"
|
||||
};
|
||||
|
||||
// when
|
||||
try (Cursor cursor = resolver.query(
|
||||
uri,
|
||||
expectedMarkersColumns,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)) {
|
||||
// then
|
||||
assertEquals("vnd.android.cursor.dir/vnd.de.dennisguse.waypoint", resolver.getType(uri));
|
||||
|
||||
assertNotNull(cursor);
|
||||
assertArrayEquals(expectedMarkersColumns, cursor.getColumnNames());
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-17
@@ -52,7 +52,6 @@ import de.dennisguse.opentracks.sensors.GpsStatusValue;
|
||||
|
||||
/**
|
||||
* Testing the states of TrackRecordingService.
|
||||
* As states are checked against MutableLiveData that are updated via `postValue()` (is asynchronous to be inform other Threads), Thread.sleep() is required.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class TrackRecordingServiceStateMachineTest {
|
||||
@@ -118,13 +117,12 @@ public class TrackRecordingServiceStateMachineTest {
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void gps_startStop() throws InterruptedException {
|
||||
public void gps_startStop() {
|
||||
// given
|
||||
assertEquals(GpsStatusValue.GPS_NONE, service.getGpsStatusObservable().getValue());
|
||||
|
||||
// when
|
||||
service.tryStartSensors();
|
||||
Thread.sleep(1000);
|
||||
|
||||
// then
|
||||
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
|
||||
@@ -133,7 +131,6 @@ public class TrackRecordingServiceStateMachineTest {
|
||||
|
||||
// when
|
||||
service.stopSensors();
|
||||
Thread.sleep(1000);
|
||||
|
||||
// then
|
||||
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
|
||||
@@ -143,13 +140,12 @@ public class TrackRecordingServiceStateMachineTest {
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void recording_startStopResume_no_data() throws InterruptedException {
|
||||
public void recording_startStopResume_no_data() {
|
||||
// given
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
// when
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
Thread.sleep(1000);
|
||||
|
||||
// then
|
||||
assertTrue(service.isRecording());
|
||||
@@ -161,7 +157,6 @@ public class TrackRecordingServiceStateMachineTest {
|
||||
|
||||
// when
|
||||
service.resumeTrack(trackId);
|
||||
Thread.sleep(1000);
|
||||
|
||||
// then
|
||||
assertTrue(service.isRecording());
|
||||
@@ -172,7 +167,6 @@ public class TrackRecordingServiceStateMachineTest {
|
||||
|
||||
// when
|
||||
service.endCurrentTrack();
|
||||
Thread.sleep(1000);
|
||||
|
||||
// then
|
||||
assertFalse(service.isRecording());
|
||||
@@ -183,12 +177,10 @@ public class TrackRecordingServiceStateMachineTest {
|
||||
|
||||
// when
|
||||
service.resumeTrack(trackId);
|
||||
Thread.sleep(1000);
|
||||
|
||||
// then
|
||||
assertTrue(service.isRecording());
|
||||
assertEquals(new RecordingStatus(trackId), service.getRecordingStatusObservable().getValue());
|
||||
Thread.sleep(1000); //TODO Figure out why we need to wait here until the update is happening
|
||||
assertNotEquals(RecordingData.NOT_RECORDING, service.getRecordingDataObservable().getValue());
|
||||
assertEquals(GpsStatusValue.GPS_ENABLED, service.getGpsStatusObservable().getValue());
|
||||
}
|
||||
@@ -214,7 +206,7 @@ public class TrackRecordingServiceStateMachineTest {
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void cannotEnd_without_starting() throws InterruptedException {
|
||||
public void cannotEnd_without_starting() {
|
||||
// given
|
||||
assertFalse(service.isRecording());
|
||||
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
|
||||
@@ -223,7 +215,6 @@ public class TrackRecordingServiceStateMachineTest {
|
||||
|
||||
// when
|
||||
service.endCurrentTrack();
|
||||
Thread.sleep(1000);
|
||||
|
||||
// then
|
||||
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
|
||||
@@ -234,33 +225,30 @@ public class TrackRecordingServiceStateMachineTest {
|
||||
@Ignore("TODO Bug: GPS can be stopped although the current track is recording")
|
||||
@MediumTest
|
||||
@Test
|
||||
public void recording_stopGPS_noop() throws InterruptedException {
|
||||
public void recording_stopGPS_noop() {
|
||||
// given
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
assertEquals(GpsStatusValue.GPS_ENABLED, service.getGpsStatusObservable().getValue());
|
||||
|
||||
// when
|
||||
service.stopSensors(); //TODO Should be ignored as service is recording
|
||||
Thread.sleep(1000);
|
||||
|
||||
// then
|
||||
assertTrue(service.isRecording());
|
||||
assertEquals(new RecordingStatus(trackId), service.getRecordingStatusObservable().getValue());
|
||||
Thread.sleep(1000); //TODO Figure out why we need to wait here until the update is happening
|
||||
assertNotEquals(RecordingData.NOT_RECORDING, service.getRecordingDataObservable().getValue());
|
||||
assertEquals(GpsStatusValue.GPS_ENABLED, service.getGpsStatusObservable().getValue());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void recording_startRecording_alreadyRecording() throws InterruptedException {
|
||||
public void recording_startRecording_alreadyRecording() {
|
||||
// given
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
assertTrue(service.isRecording());
|
||||
|
||||
// when
|
||||
Track.Id newTrackId = service.startNewTrack();
|
||||
Thread.sleep(1000);
|
||||
|
||||
// then
|
||||
assertNotNull(trackId);
|
||||
|
||||
+21
-16
@@ -14,7 +14,6 @@ import static de.dennisguse.opentracks.util.EspressoUtils.waitFor;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.espresso.ViewInteraction;
|
||||
import androidx.test.espresso.matcher.ViewMatchers;
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
@@ -61,28 +60,34 @@ public class EspressoAggregatedFilterTest {
|
||||
@Test
|
||||
public void espressoAggregatedFilterTest() {
|
||||
// open AggregatedStatisticsActivity through toolbar's menu item
|
||||
onView(ViewMatchers.withId(R.id.bottom_app_bar)).check(matches(isDisplayed()));
|
||||
onView(withId(R.id.bottom_app_bar)).check(matches(hasDescendant(withId(R.id.aggregated_stats_button))));
|
||||
onView(withId(R.id.aggregated_stats_button)).perform(click());
|
||||
onView(ViewMatchers.withId(R.id.bottom_app_bar))
|
||||
.check(matches(isDisplayed()));
|
||||
onView(withId(R.id.bottom_app_bar))
|
||||
.check(matches(hasDescendant(withId(R.id.aggregated_stats_button))));
|
||||
onView(withId(R.id.aggregated_stats_button))
|
||||
.perform(click());
|
||||
|
||||
// open FilterDialogFragment through toolbar's menu item
|
||||
onView(withId(R.id.bottom_app_bar)).check(matches(isDisplayed()));
|
||||
onView(withId(R.id.bottom_app_bar)).check(matches(hasDescendant(withId(R.id.aggregated_statistics_filter))));
|
||||
onView(withId(R.id.aggregated_statistics_filter)).perform(click());
|
||||
onView(withId(R.id.bottom_app_bar))
|
||||
.check(matches(isDisplayed()));
|
||||
onView(withId(R.id.bottom_app_bar))
|
||||
.check(matches(hasDescendant(withId(R.id.aggregated_statistics_filter))));
|
||||
onView(withId(R.id.aggregated_statistics_filter))
|
||||
.perform(click());
|
||||
|
||||
// check there's a checkbox with ACTIVITY_TYPE_LOCALIZED text
|
||||
ViewInteraction checkBox = onView(
|
||||
allOf(withId(R.id.filter_dialog_check_button), withText(ACTIVITY_TYPE_LOCALIZED),
|
||||
withParent(allOf(withId(R.id.filter_items),
|
||||
withParent(IsInstanceOf.instanceOf(android.view.ViewGroup.class)))),
|
||||
isDisplayed()));
|
||||
checkBox.perform(waitFor(2000));
|
||||
onView(allOf(withId(R.id.filter_dialog_check_button), withText(ACTIVITY_TYPE_LOCALIZED),
|
||||
withParent(allOf(withId(R.id.filter_items),
|
||||
withParent(IsInstanceOf.instanceOf(android.view.ViewGroup.class)))),
|
||||
isDisplayed()))
|
||||
.perform(waitFor(2000));
|
||||
|
||||
// check there's an edit text for "from date"
|
||||
onView(withId(R.id.filter_date_edit_text_from)).check(matches(isDisplayed()));
|
||||
onView(withId(R.id.filter_date_edit_text_from))
|
||||
.check(matches(isDisplayed()));
|
||||
|
||||
// check there's an edit text for "to date"
|
||||
onView(withId(R.id.filter_date_edit_text_to)).check(matches(isDisplayed()));
|
||||
onView(withId(R.id.filter_date_edit_text_to))
|
||||
.check(matches(isDisplayed()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ import java.util.Objects;
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.databinding.TrackListBinding;
|
||||
import de.dennisguse.opentracks.publicapi.DataProvider;
|
||||
import de.dennisguse.opentracks.sensors.GpsStatusValue;
|
||||
import de.dennisguse.opentracks.services.MissingPermissionException;
|
||||
import de.dennisguse.opentracks.services.RecordingStatus;
|
||||
@@ -62,7 +63,6 @@ import de.dennisguse.opentracks.ui.aggregatedStatistics.ConfirmDeleteDialogFragm
|
||||
import de.dennisguse.opentracks.ui.markers.MarkerListActivity;
|
||||
import de.dennisguse.opentracks.ui.util.ActivityUtils;
|
||||
import de.dennisguse.opentracks.ui.util.RecyclerViewSwipeDeleteCallback;
|
||||
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.PermissionRequester;
|
||||
|
||||
@@ -367,7 +367,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
}
|
||||
|
||||
if (itemId == R.id.list_context_menu_show_on_map) {
|
||||
IntentDashboardUtils.showTrackOnMap(this, false, trackIds);
|
||||
DataProvider.showTrackOnMap(this, false, trackIds);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ import de.dennisguse.opentracks.data.TrackDataHub;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.databinding.TrackRecordedBinding;
|
||||
import de.dennisguse.opentracks.fragments.StatisticsRecordedFragment;
|
||||
import de.dennisguse.opentracks.publicapi.DataProvider;
|
||||
import de.dennisguse.opentracks.services.RecordingStatus;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
@@ -45,7 +46,6 @@ import de.dennisguse.opentracks.share.ShareUtils;
|
||||
import de.dennisguse.opentracks.ui.aggregatedStatistics.ConfirmDeleteDialogFragment;
|
||||
import de.dennisguse.opentracks.ui.intervals.IntervalsFragment;
|
||||
import de.dennisguse.opentracks.ui.markers.MarkerListActivity;
|
||||
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
|
||||
/**
|
||||
@@ -167,7 +167,7 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
|
||||
}
|
||||
|
||||
if (item.getItemId() == R.id.track_detail_menu_show_on_map) {
|
||||
IntentDashboardUtils.showTrackOnMap(this, false, trackId);
|
||||
DataProvider.showTrackOnMap(this, false, trackId);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.databinding.TrackRecordingBinding;
|
||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||
import de.dennisguse.opentracks.fragments.StatisticsRecordingFragment;
|
||||
import de.dennisguse.opentracks.publicapi.DataProvider;
|
||||
import de.dennisguse.opentracks.sensors.GpsStatusValue;
|
||||
import de.dennisguse.opentracks.services.RecordingStatus;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
@@ -49,7 +50,6 @@ import de.dennisguse.opentracks.ui.intervals.IntervalsFragment;
|
||||
import de.dennisguse.opentracks.ui.markers.MarkerEditActivity;
|
||||
import de.dennisguse.opentracks.ui.markers.MarkerListActivity;
|
||||
import de.dennisguse.opentracks.ui.util.ActivityUtils;
|
||||
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
|
||||
/**
|
||||
@@ -267,7 +267,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == R.id.track_detail_menu_show_on_map) {
|
||||
IntentDashboardUtils.showTrackOnMap(this, true, trackId);
|
||||
DataProvider.showTrackOnMap(this, true, trackId);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -69,9 +69,11 @@ public class ContentProviderUtils {
|
||||
|
||||
private static final String TAG = ContentProviderUtils.class.getSimpleName();
|
||||
|
||||
//TODO Move to CustomContentProvider
|
||||
// The authority (the first part of the URI) for the app's content provider.
|
||||
static final String AUTHORITY_PACKAGE = BuildConfig.APPLICATION_ID + ".content";
|
||||
|
||||
//TODO Move to CustomContentProvider
|
||||
// The base URI for the app's content provider.
|
||||
public static final String CONTENT_BASE_URI = "content://" + AUTHORITY_PACKAGE;
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.data.tables.MarkerColumns;
|
||||
import de.dennisguse.opentracks.data.tables.TrackPointsColumns;
|
||||
import de.dennisguse.opentracks.data.tables.TracksColumns;
|
||||
import de.dennisguse.opentracks.publicapi.DataProvider;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
/**
|
||||
@@ -94,7 +95,6 @@ public class CustomContentProvider extends ContentProvider {
|
||||
"AND t." + TrackPointsColumns.TYPE + " NOT IN (" + TrackPoint.Type.SEGMENT_START_MANUAL.type_db + ")";
|
||||
|
||||
public CustomContentProvider() {
|
||||
//TODO Add tests for used URIs (incl. Dashboard API)
|
||||
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TrackPointsColumns.CONTENT_URI_BY_ID.getPath(), UrlType.TRACKPOINTS.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TrackPointsColumns.CONTENT_URI_BY_ID.getPath() + "/#", UrlType.TRACKPOINTS_BY_ID.ordinal());
|
||||
@@ -108,6 +108,10 @@ public class CustomContentProvider extends ContentProvider {
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, MarkerColumns.CONTENT_URI.getPath(), UrlType.MARKERS.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, MarkerColumns.CONTENT_URI.getPath() + "/#", UrlType.MARKERS_BY_ID.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, MarkerColumns.CONTENT_URI_BY_TRACKID.getPath() + "/*", UrlType.MARKERS_BY_TRACKID.ordinal());
|
||||
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, UrlType.DASHBOARD_TRACKS_BY_IDS.getPath() + "*", UrlType.DASHBOARD_TRACKS_BY_IDS.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, UrlType.DASHBOARD_TRACKPOINTS_BY_TRACKIDS.getPath() + "*", UrlType.DASHBOARD_TRACKPOINTS_BY_TRACKIDS.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, UrlType.DASHBOARD_MARKERS_BY_TRACKIDS.getPath() + "*", UrlType.DASHBOARD_MARKERS_BY_TRACKIDS.ordinal());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -170,12 +174,21 @@ public class CustomContentProvider extends ContentProvider {
|
||||
|
||||
@Override
|
||||
public String getType(@NonNull Uri url) {
|
||||
String contentType = getUrlType(url).contentType;
|
||||
if (contentType != null) {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
//TODO replace with getUrlType(url).getContentType()
|
||||
return switch (getUrlType(url)) {
|
||||
case TRACKPOINTS -> TrackPointsColumns.CONTENT_TYPE;
|
||||
case TRACKPOINTS_BY_TRACKID -> TrackPointsColumns.CONTENT_ITEMTYPE;
|
||||
case TRACKS -> TracksColumns.CONTENT_TYPE;
|
||||
case MARKERS -> MarkerColumns.CONTENT_TYPE;
|
||||
case MARKERS_BY_TRACKID -> MarkerColumns.CONTENT_ITEMTYPE;
|
||||
case DASHBOARD_TRACKS_BY_IDS -> UrlType.DASHBOARD_TRACKS_BY_IDS.contentType;
|
||||
case DASHBOARD_TRACKPOINTS_BY_TRACKIDS -> UrlType.DASHBOARD_TRACKPOINTS_BY_TRACKIDS.contentType;
|
||||
case DASHBOARD_MARKERS_BY_TRACKIDS -> UrlType.DASHBOARD_MARKERS_BY_TRACKIDS.contentType;
|
||||
default -> throw new IllegalArgumentException("Unknown URL " + url);
|
||||
};
|
||||
}
|
||||
@@ -225,6 +238,7 @@ public class CustomContentProvider extends ContentProvider {
|
||||
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
|
||||
String sortOrder = null;
|
||||
String groupBy = null;
|
||||
//TODO queryBuilder.setStrict(true);
|
||||
//TODO queryBuilder.appendWhere() should use selectionArgs?
|
||||
switch (getUrlType(url)) {
|
||||
case TRACKPOINTS -> {
|
||||
@@ -239,6 +253,11 @@ public class CustomContentProvider extends ContentProvider {
|
||||
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
|
||||
queryBuilder.appendWhere(TrackPointsColumns.TRACKID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
||||
}
|
||||
case DASHBOARD_TRACKPOINTS_BY_TRACKIDS -> {
|
||||
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
|
||||
queryBuilder.appendWhere(TrackPointsColumns.TRACKID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
||||
queryBuilder.setProjectionMap(DataProvider.DATA_PROJECTIONMAP_TRACKPOINTS);
|
||||
}
|
||||
case TRACKS -> {
|
||||
if (projection != null && Arrays.asList(projection).contains(TracksColumns.MARKER_COUNT)) {
|
||||
queryBuilder.setTables(TracksColumns.TABLE_NAME + " LEFT OUTER JOIN (SELECT " + MarkerColumns.TRACKID + " AS markerTrackId, COUNT(*) AS " + TracksColumns.MARKER_COUNT + " FROM " + MarkerColumns.TABLE_NAME + " GROUP BY " + MarkerColumns.TRACKID + ") ON (" + TracksColumns.TABLE_NAME + "." + TracksColumns._ID + "= markerTrackId)");
|
||||
@@ -251,6 +270,11 @@ public class CustomContentProvider extends ContentProvider {
|
||||
queryBuilder.setTables(TracksColumns.TABLE_NAME);
|
||||
queryBuilder.appendWhere(TracksColumns._ID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
||||
}
|
||||
case DASHBOARD_TRACKS_BY_IDS -> {
|
||||
queryBuilder.setTables(TracksColumns.TABLE_NAME);
|
||||
queryBuilder.appendWhere(TracksColumns._ID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
||||
queryBuilder.setProjectionMap(DataProvider.DATA_PROJECTIONMAP_TRACKS);
|
||||
}
|
||||
case TRACKS_SENSOR_STATS -> {
|
||||
//TODO Replace with SQLQueryBuilder?
|
||||
long trackId = ContentUris.parseId(url);
|
||||
@@ -281,6 +305,11 @@ public class CustomContentProvider extends ContentProvider {
|
||||
queryBuilder.setTables(MarkerColumns.TABLE_NAME);
|
||||
queryBuilder.appendWhere(MarkerColumns.TRACKID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
||||
}
|
||||
case DASHBOARD_MARKERS_BY_TRACKIDS -> {
|
||||
queryBuilder.setTables(MarkerColumns.TABLE_NAME);
|
||||
queryBuilder.appendWhere(MarkerColumns.TRACKID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
||||
queryBuilder.setProjectionMap(DataProvider.DATA_PROJECTIONMAP_MARKERS);
|
||||
}
|
||||
default -> throw new IllegalArgumentException("Unknown url " + url);
|
||||
}
|
||||
Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, groupBy, null, sortOrder);
|
||||
@@ -372,9 +401,11 @@ public class CustomContentProvider extends ContentProvider {
|
||||
throw new SQLException("Failed to insert a marker " + url);
|
||||
}
|
||||
|
||||
private enum UrlType {
|
||||
//TODO Add ContentType as attribute
|
||||
public enum UrlType {
|
||||
TRACKPOINTS,
|
||||
TRACKPOINTS_BY_ID,
|
||||
//TODO Check if still used
|
||||
TRACKPOINTS_BY_TRACKID,
|
||||
TRACKS,
|
||||
TRACKS_BY_ID,
|
||||
@@ -382,6 +413,36 @@ public class CustomContentProvider extends ContentProvider {
|
||||
TRACKS_AGGREGATED_STATISTICS,
|
||||
MARKERS,
|
||||
MARKERS_BY_ID,
|
||||
MARKERS_BY_TRACKID
|
||||
@Deprecated //TODO Should not be used anymore
|
||||
MARKERS_BY_TRACKID,
|
||||
|
||||
DASHBOARD_TRACKS_BY_IDS(Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/dashboard/tracks/"), TracksColumns.CONTENT_TYPE),
|
||||
DASHBOARD_TRACKPOINTS_BY_TRACKIDS(Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/dashboard/trackpoints/"), TrackPointsColumns.CONTENT_TYPE),
|
||||
DASHBOARD_MARKERS_BY_TRACKIDS(Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/dashboard/markers/"), MarkerColumns.CONTENT_TYPE);
|
||||
|
||||
private final Uri uri;
|
||||
|
||||
private final String contentType;
|
||||
|
||||
//TODO Add Uri as attribute:
|
||||
//TODO Add ContentType as attribute:
|
||||
@Deprecated()
|
||||
UrlType() {
|
||||
this.uri = null;
|
||||
this.contentType = null;
|
||||
}
|
||||
|
||||
UrlType(Uri uri, String contentType) {
|
||||
this.uri = uri;
|
||||
this.contentType = contentType;
|
||||
}
|
||||
|
||||
public Uri getUri() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
String getPath() {
|
||||
return uri.getPath();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,11 +89,10 @@ public class ShareContentProvider extends CustomContentProvider {
|
||||
trackIdBuilder.deleteCharAt(trackIdBuilder.lastIndexOf(TRACKID_DELIMITER));
|
||||
|
||||
Uri uri = Uri.parse(TracksColumns.CONTENT_URI + "/" + trackFileFormat.getPreferenceId() + "/" + trackIdBuilder + "/" + Uri.encode(filename));
|
||||
String mime = getTypeMime(uri);
|
||||
|
||||
Log.d(TAG, "Created uri " + uri + " with MIME " + mime);
|
||||
Log.d(TAG, "Created uri " + uri + " with MIME " + trackFileFormat.getMimeType());
|
||||
|
||||
return new Pair<>(uri, mime);
|
||||
return new Pair<>(uri, trackFileFormat.getMimeType());
|
||||
}
|
||||
|
||||
static Set<Track.Id> parseURI(Uri uri) {
|
||||
@@ -126,6 +125,7 @@ public class ShareContentProvider extends CustomContentProvider {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static TrackFileFormat getTrackFileFormat(@NonNull Uri uri) {
|
||||
return switch (uriMatcher.match(uri)) {
|
||||
case URI_GPX -> TrackFileFormat.GPX;
|
||||
@@ -134,16 +134,10 @@ public class ShareContentProvider extends CustomContentProvider {
|
||||
case URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES ->
|
||||
TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES;
|
||||
case URI_CSV -> TrackFileFormat.CSV;
|
||||
default ->
|
||||
throw new RuntimeException("Could not derive TrackFileFormat from Uri " + uri);
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String getTypeMime(@NonNull Uri uri) {
|
||||
return getTrackFileFormat(uri).getMimeType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
|
||||
if (uriMatcher.match(uri) == -1) {
|
||||
@@ -182,9 +176,9 @@ public class ShareContentProvider extends CustomContentProvider {
|
||||
@Nullable
|
||||
@Override
|
||||
public String getType(@NonNull Uri uri) {
|
||||
String mime = getTypeMime(uri);
|
||||
if (mime != null) {
|
||||
return mime;
|
||||
TrackFileFormat trackFileFormat = getTrackFileFormat(uri);
|
||||
if (trackFileFormat != null) {
|
||||
return trackFileFormat.getMimeType();
|
||||
}
|
||||
|
||||
return super.getType(uri);
|
||||
|
||||
@@ -5,7 +5,6 @@ import android.content.res.Resources;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
@@ -31,11 +30,13 @@ public enum TrackFileFormat {
|
||||
return new KMLTrackExporter(context, contentProviderUtils, false);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getMimeType() {
|
||||
return "application/vnd.google-earth.kml+xml";
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getExtension() {
|
||||
return "kml";
|
||||
}
|
||||
@@ -50,11 +51,13 @@ public enum TrackFileFormat {
|
||||
return new KMZTrackExporter(context, contentProviderUtils, exportPhotos);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getMimeType() {
|
||||
return "application/vnd.google-earth.kmz";
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getExtension() {
|
||||
return "kmz";
|
||||
}
|
||||
@@ -72,11 +75,13 @@ public enum TrackFileFormat {
|
||||
return new GPXTrackExporter(contentProviderUtils, context.getString(R.string.app_name));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getMimeType() {
|
||||
return "application/gpx+xml";
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getExtension() {
|
||||
return "gpx";
|
||||
}
|
||||
@@ -88,11 +93,13 @@ public enum TrackFileFormat {
|
||||
return new CSVTrackExporter(contentProviderUtils);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getMimeType() {
|
||||
return "text/csv";
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getExtension() {
|
||||
return "csv";
|
||||
@@ -115,16 +122,10 @@ public enum TrackFileFormat {
|
||||
return preferenceIdLabelMap;
|
||||
}
|
||||
|
||||
public static TrackFileFormat valueOfPreferenceId(final String preferenceId) {
|
||||
return Arrays.stream(values())
|
||||
.filter(trackFileFormat -> trackFileFormat.getPreferenceId().equals(preferenceId))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mime type for each format.
|
||||
*/
|
||||
@NonNull
|
||||
public abstract String getMimeType();
|
||||
|
||||
/**
|
||||
@@ -137,6 +138,7 @@ public enum TrackFileFormat {
|
||||
/**
|
||||
* Returns the file extension for each format.
|
||||
*/
|
||||
@NonNull
|
||||
public abstract String getExtension();
|
||||
|
||||
/**
|
||||
@@ -149,6 +151,7 @@ public enum TrackFileFormat {
|
||||
/**
|
||||
* The identifier to be stored in the preferences.
|
||||
*/
|
||||
@NonNull
|
||||
public String getPreferenceId() {
|
||||
return preferenceId;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
package de.dennisguse.opentracks.publicapi;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ClipData;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.CustomContentProvider;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.tables.MarkerColumns;
|
||||
import de.dennisguse.opentracks.data.tables.TrackPointsColumns;
|
||||
import de.dennisguse.opentracks.data.tables.TracksColumns;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
/**
|
||||
* Create an {@link Intent} to request showing tracks on a Map or a Dashboard.
|
||||
* The receiving {@link android.app.Activity} gets temporary access to the {@link TracksColumns} and the {@link TrackPointsColumns} (incl. update).
|
||||
*/
|
||||
public class DataProvider {
|
||||
|
||||
private static final String TAG = DataProvider.class.getSimpleName();
|
||||
|
||||
private static final String ACTION_DASHBOARD = "Intent.OpenTracks-Dashboard";
|
||||
|
||||
private static final String ACTION_DASHBOARD_PAYLOAD = ACTION_DASHBOARD + ".Payload";
|
||||
|
||||
/**
|
||||
* Assume "1" if not present.
|
||||
*/
|
||||
private static final String EXTRAS_PROTOCOL_VERSION = "PROTOCOL_VERSION";
|
||||
|
||||
/**
|
||||
* version 1: the initial version.
|
||||
* version 2: replaced pause/resume trackpoints for track segmentation (lat=100 / lat=200) by TrackPoint.Type.
|
||||
* version 3: renamed track database columns.
|
||||
*/
|
||||
private static final int CURRENT_VERSION = 2;
|
||||
|
||||
private static final String EXTRAS_OPENTRACKS_IS_RECORDING_THIS_TRACK = "EXTRAS_OPENTRACKS_IS_RECORDING_THIS_TRACK";
|
||||
private static final String EXTRAS_SHOULD_KEEP_SCREEN_ON = "EXTRAS_SHOULD_KEEP_SCREEN_ON";
|
||||
private static final String EXTRAS_SHOW_WHEN_LOCKED = "EXTRAS_SHOULD_KEEP_SCREEN_ON";
|
||||
private static final String EXTRAS_SHOW_FULLSCREEN = "EXTRAS_SHOULD_FULLSCREEN";
|
||||
|
||||
private static final int TRACK_URI_INDEX = 0;
|
||||
private static final int TRACKPOINTS_URI_INDEX = 1;
|
||||
private static final int MARKERS_URI_INDEX = 2;
|
||||
|
||||
public static final Map<String, String> DATA_PROJECTIONMAP_TRACKS = Map.ofEntries(
|
||||
//V2
|
||||
Map.entry("_id", TracksColumns._ID),
|
||||
Map.entry("name", TracksColumns.NAME),
|
||||
Map.entry("description", TracksColumns.DESCRIPTION),
|
||||
Map.entry("category", TracksColumns.ACTIVITY_TYPE + " AS category"),
|
||||
Map.entry("activity_type_localized", TracksColumns.ACTIVITY_TYPE_LOCALIZED),
|
||||
Map.entry("starttime", TracksColumns.STARTTIME + " AS starttime"),
|
||||
Map.entry("stoptime", TracksColumns.STOPTIME + " AS stoptime"),
|
||||
Map.entry("totaltime", TracksColumns.TOTALTIME + " AS totaltime"),
|
||||
Map.entry("movingtime", TracksColumns.MOVINGTIME + " AS movingtime"),
|
||||
Map.entry("totaldistance", TracksColumns.TOTALDISTANCE + " AS totaldistance"),
|
||||
Map.entry("maxspeed", TracksColumns.MAXSPEED + " AS maxspeed"),
|
||||
Map.entry("avgspeed", "null AS avgspeed"),
|
||||
Map.entry("avgmovingspeed", "null AS avgmovingspeed"),
|
||||
Map.entry("minelevation", TracksColumns.MIN_ALTITUDE + " AS minelevation"),
|
||||
Map.entry("maxelevation", TracksColumns.MAX_ALTITUDE + " AS maxelevation"),
|
||||
Map.entry("elevationgain", TracksColumns.ALTITUDE_GAIN + " AS elevationgain"),
|
||||
Map.entry("elevationloss", TracksColumns.ALTITUDE_LOSS + " AS elevationloss"),
|
||||
|
||||
//V3
|
||||
// Map.entry("_id", TracksColumns._ID),
|
||||
// Map.entry("name",TracksColumns.NAME),
|
||||
// Map.entry("description",TracksColumns.DESCRIPTION),
|
||||
Map.entry("activity_type", TracksColumns.ACTIVITY_TYPE),
|
||||
// Map.entry("activity_type_localized",TracksColumns.ACTIVITY_TYPE_LOCALIZED),
|
||||
Map.entry("time_start", TracksColumns.STARTTIME),
|
||||
Map.entry("time_stop", TracksColumns.STOPTIME),
|
||||
Map.entry("duration_total", TracksColumns.TOTALTIME),
|
||||
Map.entry("duration_moving", TracksColumns.MOVINGTIME),
|
||||
Map.entry("distance", TracksColumns.TOTALDISTANCE),
|
||||
Map.entry("speed_max", TracksColumns.MAXSPEED),
|
||||
Map.entry("altitude_min", TracksColumns.MIN_ALTITUDE),
|
||||
Map.entry("altitude_max", TracksColumns.MAX_ALTITUDE),
|
||||
Map.entry("altitude_gain", TracksColumns.ALTITUDE_GAIN),
|
||||
Map.entry("altitude_loss", TracksColumns.ALTITUDE_LOSS)
|
||||
);
|
||||
|
||||
public static final Map<String, String> DATA_PROJECTIONMAP_TRACKPOINTS = Map.ofEntries(
|
||||
//V2 and V3
|
||||
Map.entry("_id", TrackPointsColumns._ID),
|
||||
Map.entry("trackid", TrackPointsColumns.TRACKID),
|
||||
Map.entry("latitude", TrackPointsColumns.LATITUDE),
|
||||
Map.entry("longitude", TrackPointsColumns.LONGITUDE),
|
||||
Map.entry("time", TrackPointsColumns.TIME),
|
||||
Map.entry("type", TrackPointsColumns.TYPE),
|
||||
Map.entry("speed", TrackPointsColumns.SPEED)
|
||||
);
|
||||
public static final Map<String, String> DATA_PROJECTIONMAP_MARKERS = Map.ofEntries(
|
||||
//V2
|
||||
Map.entry("_id", MarkerColumns._ID),
|
||||
Map.entry("trackid", MarkerColumns.TRACKID),
|
||||
Map.entry("name", MarkerColumns.NAME),
|
||||
Map.entry("description", MarkerColumns.DESCRIPTION),
|
||||
Map.entry("category", MarkerColumns.TYPE_LOCALIZED + " AS category"),
|
||||
Map.entry("latitude", MarkerColumns.LATITUDE),
|
||||
Map.entry("longitude", MarkerColumns.LONGITUDE),
|
||||
Map.entry("photoUrl", MarkerColumns.PHOTOURL),
|
||||
Map.entry("icon", "null AS icon"),
|
||||
|
||||
//V3
|
||||
// Map.entry("_id", MarkerColumns._ID),
|
||||
// Map.entry("trackid",MarkerColumns.TRACKID),
|
||||
// Map.entry("name",MarkerColumns.NAME),
|
||||
// Map.entry("description",MarkerColumns.DESCRIPTION),
|
||||
Map.entry("marker_type_localized", MarkerColumns.TYPE_LOCALIZED)
|
||||
// Map.entry("latitude",MarkerColumns.LATITUDE),
|
||||
// Map.entry("longitude",MarkerColumns.LONGITUDE),
|
||||
// Map.entry("photoUrl",MarkerColumns.PHOTOURL)
|
||||
);
|
||||
|
||||
|
||||
private DataProvider() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Send intent to show tracks on a map (needs an another app) as resource URIs.
|
||||
*
|
||||
* @param context the context
|
||||
* @param isRecording are we currently recording?
|
||||
* @param trackIds the track ids
|
||||
*/
|
||||
public static void showTrackOnMap(Context context, boolean isRecording, Track.Id... trackIds) {
|
||||
startDashboard(context, isRecording, null, null, trackIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send intent to show tracks on a map (needs an another app) as resource URIs.
|
||||
* By providing a targetPackage and targetClass an explicit intent can be sent,
|
||||
* thus bypassing the need for the user to select an app.
|
||||
*
|
||||
* @param context the context
|
||||
* @param isRecording are we currently recording?
|
||||
* @param targetPackage the target package
|
||||
* @param targetClass the target class
|
||||
* @param trackIds the track ids
|
||||
*/
|
||||
public static void startDashboard(Context context, boolean isRecording, @Nullable String targetPackage, @Nullable String targetClass, Track.Id... trackIds) {
|
||||
if (trackIds.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
String trackIdList = ContentProviderUtils.formatIdListForUri(trackIds);
|
||||
|
||||
ArrayList<Uri> uris = new ArrayList<>();
|
||||
uris.add(TRACK_URI_INDEX, Uri.withAppendedPath(CustomContentProvider.UrlType.DASHBOARD_TRACKS_BY_IDS.getUri(), trackIdList));
|
||||
uris.add(TRACKPOINTS_URI_INDEX, Uri.withAppendedPath(CustomContentProvider.UrlType.DASHBOARD_TRACKPOINTS_BY_TRACKIDS.getUri(), trackIdList));
|
||||
uris.add(MARKERS_URI_INDEX, Uri.withAppendedPath(CustomContentProvider.UrlType.DASHBOARD_MARKERS_BY_TRACKIDS.getUri(), trackIdList));
|
||||
|
||||
Intent intent = new Intent(ACTION_DASHBOARD);
|
||||
intent.putExtra(EXTRAS_PROTOCOL_VERSION, CURRENT_VERSION);
|
||||
|
||||
intent.putParcelableArrayListExtra(ACTION_DASHBOARD_PAYLOAD, uris);
|
||||
|
||||
intent.putExtra(EXTRAS_SHOULD_KEEP_SCREEN_ON, PreferencesUtils.shouldKeepScreenOn());
|
||||
intent.putExtra(EXTRAS_SHOW_WHEN_LOCKED, PreferencesUtils.shouldShowStatsOnLockscreen());
|
||||
intent.putExtra(EXTRAS_OPENTRACKS_IS_RECORDING_THIS_TRACK, isRecording);
|
||||
if (isRecording) {
|
||||
intent.putExtra(EXTRAS_SHOW_FULLSCREEN, PreferencesUtils.shouldUseFullscreen());
|
||||
}
|
||||
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
ClipData clipData = ClipData.newRawUri(null, uris.get(TRACK_URI_INDEX));
|
||||
clipData.addItem(new ClipData.Item(uris.get(TRACKPOINTS_URI_INDEX)));
|
||||
clipData.addItem(new ClipData.Item(uris.get(MARKERS_URI_INDEX)));
|
||||
intent.setClipData(clipData);
|
||||
|
||||
if (targetPackage != null && targetClass != null) {
|
||||
Log.i(TAG, "Starting dashboard activity with explicit intent (package=" + targetPackage + ", class=" + targetClass + ")");
|
||||
intent.setClassName(targetPackage, targetClass);
|
||||
} else {
|
||||
Log.i(TAG, "Starting dashboard activity with generic intent (package=" + targetPackage + ", class=" + targetClass + ")");
|
||||
}
|
||||
|
||||
try {
|
||||
context.startActivity(intent);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Log.e(TAG, "Dashboard not installed; cannot start it.");
|
||||
Toast.makeText(context, R.string.show_on_dashboard_not_installed, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@ import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.models.TrackBuilder;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
||||
|
||||
public class StartRecording extends AbstractAPIActivity {
|
||||
|
||||
@@ -61,7 +60,7 @@ public class StartRecording extends AbstractAPIActivity {
|
||||
String targetPackage = bundle.getString(EXTRA_STATS_TARGET_PACKAGE, null);
|
||||
String targetClass = bundle.getString(EXTRA_STATS_TARGET_CLASS, null);
|
||||
if (targetClass != null && targetPackage != null) {
|
||||
IntentDashboardUtils.startDashboard(this, true, targetPackage, targetClass, trackId);
|
||||
DataProvider.startDashboard(this, true, targetPackage, targetClass, trackId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -185,14 +185,15 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
||||
}
|
||||
|
||||
private void startRecording() {
|
||||
// Update instance variables
|
||||
handler.postDelayed(updateRecordingData, RECORDING_DATA_UPDATE_INTERVAL.toMillis());
|
||||
|
||||
startSensors();
|
||||
|
||||
voiceAnnouncementManager.start(trackRecordingManager.getTrackStatistics());
|
||||
|
||||
notificationManager.onRecordingStarted(this, recordingStatus.trackId());
|
||||
|
||||
// Update instance variables
|
||||
updateRecordingData.run();
|
||||
handler.postDelayed(updateRecordingData, RECORDING_DATA_UPDATE_INTERVAL.toMillis());
|
||||
}
|
||||
|
||||
public void tryStartSensors() {
|
||||
|
||||
@@ -778,7 +778,14 @@ public class PreferencesUtils {
|
||||
// We want to really clear settings now.
|
||||
sharedPreferences.edit().clear().commit();
|
||||
}
|
||||
|
||||
// Each setting file must be mentioned here.
|
||||
PreferenceManager.setDefaultValues(context, R.xml.settings, readAgain);
|
||||
PreferenceManager.setDefaultValues(context, R.xml.settings_defaults, readAgain);
|
||||
PreferenceManager.setDefaultValues(context, R.xml.settings_user_interface, readAgain);
|
||||
PreferenceManager.setDefaultValues(context, R.xml.settings_sensors, readAgain);
|
||||
PreferenceManager.setDefaultValues(context, R.xml.settings_import_export, readAgain);
|
||||
PreferenceManager.setDefaultValues(context, R.xml.settings_public_api, readAgain);
|
||||
}
|
||||
|
||||
public static Uri getDefaultExportDirectoryUri() {
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ClipData;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.tables.MarkerColumns;
|
||||
import de.dennisguse.opentracks.data.tables.TrackPointsColumns;
|
||||
import de.dennisguse.opentracks.data.tables.TracksColumns;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
/**
|
||||
* Create an {@link Intent} to request showing tracks on a Map or a Dashboard.
|
||||
* The receiving {@link android.app.Activity} gets temporary access to the {@link TracksColumns} and the {@link TrackPointsColumns} (incl. update).
|
||||
*/
|
||||
//TODO Add a test for the table structure exposed via the DashboardAPI.
|
||||
public class IntentDashboardUtils {
|
||||
|
||||
private static final String TAG = IntentDashboardUtils.class.getSimpleName();
|
||||
|
||||
private static final String ACTION_DASHBOARD = "Intent.OpenTracks-Dashboard";
|
||||
|
||||
private static final String ACTION_DASHBOARD_PAYLOAD = ACTION_DASHBOARD + ".Payload";
|
||||
|
||||
/**
|
||||
* Assume "v1" if not present.
|
||||
*/
|
||||
private static final String EXTRAS_PROTOCOL_VERSION = "PROTOCOL_VERSION";
|
||||
|
||||
/**
|
||||
* version 1: the initial version.
|
||||
* version 2: replaced pause/resume trackpoints for track segmentation (lat=100 / lat=200) by TrackPoint.Type.
|
||||
* version 3: renamed track database columns.
|
||||
*/
|
||||
private static final int CURRENT_VERSION = 3;
|
||||
|
||||
private static final String EXTRAS_OPENTRACKS_IS_RECORDING_THIS_TRACK = "EXTRAS_OPENTRACKS_IS_RECORDING_THIS_TRACK";
|
||||
private static final String EXTRAS_SHOULD_KEEP_SCREEN_ON = "EXTRAS_SHOULD_KEEP_SCREEN_ON";
|
||||
private static final String EXTRAS_SHOW_WHEN_LOCKED = "EXTRAS_SHOULD_KEEP_SCREEN_ON";
|
||||
private static final String EXTRAS_SHOW_FULLSCREEN = "EXTRAS_SHOULD_FULLSCREEN";
|
||||
|
||||
private static final int TRACK_URI_INDEX = 0;
|
||||
private static final int TRACKPOINTS_URI_INDEX = 1;
|
||||
private static final int MARKERS_URI_INDEX = 2;
|
||||
private static final int NONE_SELECTED = -1;
|
||||
|
||||
private IntentDashboardUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Send intent to show tracks on a map (needs an another app) as resource URIs.
|
||||
*
|
||||
* @param context the context
|
||||
* @param isRecording are we currently recording?
|
||||
* @param trackIds the track ids
|
||||
*/
|
||||
public static void showTrackOnMap(Context context, boolean isRecording, Track.Id... trackIds) {
|
||||
startDashboard(context, isRecording, null, null, trackIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send intent to show tracks on a map (needs an another app) as resource URIs.
|
||||
* By providing a targetPackage and targetClass an explicit intent can be sent,
|
||||
* thus bypassing the need for the user to select an app.
|
||||
*
|
||||
* @param context the context
|
||||
* @param isRecording are we currently recording?
|
||||
* @param targetPackage the target package
|
||||
* @param targetClass the target class
|
||||
* @param trackIds the track ids
|
||||
*/
|
||||
public static void startDashboard(Context context, boolean isRecording, @Nullable String targetPackage, @Nullable String targetClass, Track.Id... trackIds) {
|
||||
if (trackIds.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
String trackIdList = ContentProviderUtils.formatIdListForUri(trackIds);
|
||||
|
||||
ArrayList<Uri> uris = new ArrayList<>();
|
||||
uris.add(TRACK_URI_INDEX, Uri.withAppendedPath(TracksColumns.CONTENT_URI, trackIdList));
|
||||
uris.add(TRACKPOINTS_URI_INDEX, Uri.withAppendedPath(TrackPointsColumns.CONTENT_URI_BY_TRACKID, trackIdList));
|
||||
uris.add(MARKERS_URI_INDEX, Uri.withAppendedPath(MarkerColumns.CONTENT_URI_BY_TRACKID, trackIdList));
|
||||
|
||||
Intent intent = new Intent(ACTION_DASHBOARD);
|
||||
intent.putExtra(EXTRAS_PROTOCOL_VERSION, CURRENT_VERSION);
|
||||
|
||||
intent.putParcelableArrayListExtra(ACTION_DASHBOARD_PAYLOAD, uris);
|
||||
|
||||
intent.putExtra(EXTRAS_SHOULD_KEEP_SCREEN_ON, PreferencesUtils.shouldKeepScreenOn());
|
||||
intent.putExtra(EXTRAS_SHOW_WHEN_LOCKED, PreferencesUtils.shouldShowStatsOnLockscreen());
|
||||
intent.putExtra(EXTRAS_OPENTRACKS_IS_RECORDING_THIS_TRACK, isRecording);
|
||||
if (isRecording) {
|
||||
intent.putExtra(EXTRAS_SHOW_FULLSCREEN, PreferencesUtils.shouldUseFullscreen());
|
||||
}
|
||||
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
ClipData clipData = ClipData.newRawUri(null, uris.get(TRACK_URI_INDEX));
|
||||
clipData.addItem(new ClipData.Item(uris.get(TRACKPOINTS_URI_INDEX)));
|
||||
clipData.addItem(new ClipData.Item(uris.get(MARKERS_URI_INDEX)));
|
||||
intent.setClipData(clipData);
|
||||
|
||||
if (targetPackage != null && targetClass != null) {
|
||||
Log.i(TAG, "Starting dashboard activity with explicit intent (package=" + targetPackage + ", class=" + targetClass + ")");
|
||||
intent.setClassName(targetPackage, targetClass);
|
||||
} else {
|
||||
Log.i(TAG, "Starting dashboard activity with generic intent (package=" + targetPackage + ", class=" + targetClass + ")");
|
||||
}
|
||||
|
||||
try {
|
||||
context.startActivity(intent);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Log.e(TAG, "Dashboard not installed; cannot start it.");
|
||||
Toast.makeText(context, R.string.show_on_dashboard_not_installed, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ limitations under the License.
|
||||
<string name="activity_type_airplane">αεροπλάνο</string>
|
||||
<string name="activity_type_atv">ATV</string>
|
||||
<string name="activity_type_biking">ποδηλασία</string>
|
||||
<string name="activity_type_blimp">μικρό αερόστατο</string>
|
||||
<string name="activity_type_blimp">αερόπλοιο</string>
|
||||
<string name="activity_type_boat">πλοίο</string>
|
||||
<string name="activity_type_climbing">αναρρίχηση</string>
|
||||
<string name="activity_type_commercial_airplane">εμπορικό αεροπλάνο</string>
|
||||
@@ -29,8 +29,8 @@ limitations under the License.
|
||||
<string name="activity_type_driving">οδήγηση</string>
|
||||
<string name="activity_type_driving_bus">οδήγηση λεωφορείου</string>
|
||||
<string name="activity_type_driving_car">οδήγηση αυτοκινήτου</string>
|
||||
<string name="activity_type_ferry">Φέρι μποτ</string>
|
||||
<string name="activity_type_frisbee">φρισμπι</string>
|
||||
<string name="activity_type_ferry">φέρι μποτ</string>
|
||||
<string name="activity_type_frisbee">φρίσμπι</string>
|
||||
<string name="activity_type_gliding">ανεμόπτερο</string>
|
||||
<string name="activity_type_hang_gliding">αιωροπτερισμός</string>
|
||||
<string name="activity_type_helicopter">ελικόπτερο</string>
|
||||
@@ -44,15 +44,15 @@ limitations under the License.
|
||||
<string name="activity_type_land_sailing">ιστιοπλοΐα γης</string>
|
||||
<string name="activity_type_mixed_type">μικτού τύπου</string>
|
||||
<string name="activity_type_motor_bike">μηχανάκι</string>
|
||||
<string name="activity_type_motor_boating">Μηχανοκίνητα σκάφη</string>
|
||||
<string name="activity_type_mountain_biking">Ποδηλασία στο βουνό</string>
|
||||
<string name="activity_type_motor_boating">μηχανοκίνητο σκάφος</string>
|
||||
<string name="activity_type_mountain_biking">ποδηλασία στο βουνό</string>
|
||||
<string name="activity_type_off_trail_hiking">πεζοπορία σε ανώμαλο έδαφος</string>
|
||||
<string name="activity_type_other">Άλλη</string>
|
||||
<string name="activity_type_other">άλλη</string>
|
||||
<string name="activity_type_paddling">κωπηλασία</string>
|
||||
<string name="activity_type_para_gliding">παραπέντε</string>
|
||||
<string name="activity_type_rc_airplane">τηλεχειριζόμενο αεροπλάνο</string>
|
||||
<string name="activity_type_rc_boat">Τηλεχειριζόμενη βάρκα</string>
|
||||
<string name="activity_type_rc_helicopter">Τηλεχειριζόμενο ελικόπτερο</string>
|
||||
<string name="activity_type_rc_boat">τηλεχειριζόμενη βάρκα</string>
|
||||
<string name="activity_type_rc_helicopter">τηλεχειριζόμενο ελικόπτερο</string>
|
||||
<string name="activity_type_riding">ιππασία</string>
|
||||
<string name="activity_type_road_biking">ποδηλασία</string>
|
||||
<string name="activity_type_roller_skiing">ρόλερ σκι ρολό</string>
|
||||
@@ -302,4 +302,27 @@ limitations under the License.
|
||||
<string name="waypoint_type_waterfall">καταρράκτης</string>
|
||||
<string name="waypoint_type_water_fountain">συντριβάνι</string>
|
||||
<string name="waypoint_type_waypoint">σημείο διαδρομής</string>
|
||||
</resources>
|
||||
<string name="about_preference_title">Σχετικά</string>
|
||||
<string name="about_url">Ιστότοπους εγχειρήματος: <a href="%1$s">%1$s</a></string>
|
||||
<string name="about_version_name">Όνομα έκδοσης: %1$s</string>
|
||||
<string name="about_version_code">Κωδικός έκδοσης: %1$d</string>
|
||||
<string name="about_third_party_data">Δεδομένα από τρίτους</string>
|
||||
<string name="about_libraries">Βιβλιοθήκες</string>
|
||||
<string name="help_feature_title">Το OpenTracks έχει τα ακόλουθα χαρακτηριστικά:</string>
|
||||
<string name="help_data_google_play_services_title">Το OpenTracks χρειάζεται/χρησιμοποιεί τις Υπηρεσίες Google Play;</string>
|
||||
<string name="help_data_google_play_services_content">Όχι</string>
|
||||
<string name="help_recording_title">Πώς το OpenTracks καταγράφει τα δεδομένα;</string>
|
||||
<string name="help_track_map_title">Πώς να εμφανιστεί ένα ίχνος στον χάρτη;</string>
|
||||
<string name="help_gps_troubleshooting_title">Το OpenTracks δεν καταγράφει τοποθεσίες;</string>
|
||||
<string name="help_support_title">Πώς να υποστηρίξω το OpenTracks;</string>
|
||||
<string name="open_track_as_trackfileformat">Ανοίγμα ως %1$s</string>
|
||||
<string name="activity_type_escooter">ηλεκτρικό πατίνι</string>
|
||||
<string name="activity_type_kickscooter">πατίνι</string>
|
||||
<string name="power_x_watt">Ισχύς {x, plural, =1 {1 watt} other {# watts} }</string>
|
||||
<string name="power">Ισχύς</string>
|
||||
<string name="report_bug">Αναφορά προβλήματος</string>
|
||||
<string name="always">Πάντα</string>
|
||||
<string name="settings_recording_min_sampling_interval_title">Χρονικό μεσοδιάστημα δειγματοληψίας</string>
|
||||
<string name="settings_recording_location_distance_summary">%1$s μεταξύ καταγεγραμμένων τοποθεσιών</string>
|
||||
<string name="field_remove_from_layout">Αφαίρεση</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user