Compare commits

...

18 Commits

Author SHA1 Message Date
Dennis Guse 45874d3749 Release: v4.0.7 2022-08-21 21:55:19 +02:00
Peter Storch 594f69320a Fix dependencies to lifecycle-viewmodel 2022-08-21 19:28:07 +02:00
dependabot[bot] d4927c9444 Bump appcompat from 1.4.2 to 1.5.0
Bumps appcompat from 1.4.2 to 1.5.0.

---
updated-dependencies:
- dependency-name: androidx.appcompat:appcompat
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-21 19:28:07 +02:00
Alexander Ivanov 00dbd056d6 Translated using Weblate (Russian)
Currently translated at 99.0% (496 of 501 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/ru/
2022-08-21 18:53:26 +02:00
M K 8f19ad8f66 Translated using Weblate (Arabic)
Currently translated at 53.6% (269 of 501 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/ar/
2022-08-21 18:53:26 +02:00
mondstern 2fdad54a7c Translated using Weblate (German)
Currently translated at 94.8% (475 of 501 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/de/
2022-08-21 18:53:26 +02:00
Peter Storch 5a5f040ed5 Fix #1340 mass track deletion time 2022-08-21 18:53:20 +02:00
Dennis Guse 0ada9a2463 Android13: request NOTIFICATION_POST permission.
Fixes #1353.
Part of #1210.
2022-08-18 08:07:37 +02:00
Dennis Guse 719bb981ec Bugfix: Public API stop does not start service in foreground.
Fixes #1354.
2022-08-17 18:43:27 +02:00
dependabot[bot] 3c95d8744d Bump mockito-android from 4.6.1 to 4.7.0
Bumps [mockito-android](https://github.com/mockito/mockito) from 4.6.1 to 4.7.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v4.6.1...v4.7.0)

---
updated-dependencies:
- dependency-name: org.mockito:mockito-android
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-15 07:59:40 +02:00
Dennis Guse 63d1c9ee5e GPS: set signal lost threshold to 30s. 2022-08-14 20:18:37 +02:00
Dennis Guse 1ccd2e3c0a Release: v4.0.6 2022-08-07 19:47:54 +02:00
Dennis Guse cdbddabc1b Cleanup: use ContextCompat.getColor() 2022-08-04 22:11:44 +02:00
Dennis Guse 9f6ca83f27 Bug: PublicAPI needs to call startForegroundService (SDK API26+).
Fixes #1301.
2022-08-04 22:11:44 +02:00
dependabot[bot] f2ee99a477 Bump gradle from 7.2.1 to 7.2.2
Bumps gradle from 7.2.1 to 7.2.2.

---
updated-dependencies:
- dependency-name: com.android.tools.build:gradle
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-04 06:44:02 +02:00
Dennis Guse 7009ede095 EGM2008 offset lookup for negative longitudes was wrong. 2022-08-03 17:30:31 +02:00
Dennis Guse 2b530a1c53 Cleanup: unused imports. 2022-08-02 18:44:15 +02:00
Poussinou 33673f5921 Update FUNDING.yml 2022-07-31 14:59:56 +02:00
74 changed files with 370 additions and 328 deletions
+1 -1
View File
@@ -2,4 +2,4 @@
liberapay: OpenTracks
github: [rgmf, pstorch, dennisguse]
custom: [ https://liberapay.com/OpenTracks, https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore ]
custom: https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore
+7 -5
View File
@@ -6,7 +6,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.2.1'
classpath 'com.android.tools.build:gradle:7.2.2'
}
}
@@ -59,8 +59,8 @@ android {
defaultConfig {
applicationId "de.dennisguse.opentracks"
versionCode 4986
versionName "v4.0.5"
versionCode 5004
versionName "v4.0.7"
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
@@ -120,7 +120,9 @@ android {
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.6'
implementation 'androidx.appcompat:appcompat:1.4.2'
implementation 'androidx.appcompat:appcompat:1.5.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.5.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
implementation 'androidx.preference:preference:1.2.0'
implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'com.google.android.material:material:1.6.1'
@@ -131,7 +133,7 @@ dependencies {
androidTestImplementation 'androidx.test:rules:1.4.0'
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation 'org.mockito:mockito-android:4.6.1'
androidTestImplementation 'org.mockito:mockito-android:4.7.0'
androidTestUtil 'androidx.test:orchestrator:1.4.1'
}
@@ -0,0 +1,6 @@
v4.0.6: OpenTracks
Bugfix:
- EGM2008 offset computed wrong for negative longitudes
- PublicAPI crashed if OpenTracks was in background (API26+)
@@ -0,0 +1,11 @@
v4.0.7: OpenTracks
Changes:
- GPS signal lost timeout: 30s
- Android13: request permission to show notifications
- Faster deletion of tracks (vacuum database only if needed)
Bugfix:
- PublicAPI stop command resulted in crash (introduced in v4.0.6)
- Permissions: request all permissions during on startup
@@ -3,48 +3,25 @@ package de.dennisguse.opentracks;
import static androidx.test.espresso.Espresso.onData;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.Espresso.pressBack;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.longClick;
import static androidx.test.espresso.action.ViewActions.scrollTo;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.anything;
import static de.dennisguse.opentracks.util.EspressoUtils.childAtPosition;
import static de.dennisguse.opentracks.util.EspressoUtils.waitFor;
import static de.dennisguse.opentracks.util.EspressoUtils.withListSize;
import android.app.ActivityManager;
import android.app.Instrumentation;
import android.content.Context;
import android.view.View;
import android.widget.ListView;
import androidx.test.espresso.IdlingRegistry;
import androidx.test.espresso.IdlingResource;
import androidx.test.espresso.ViewInteraction;
import androidx.test.espresso.assertion.ViewAssertions;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.GrantPermissionRule;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import de.dennisguse.opentracks.services.TrackDeleteService;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class EspressoDeleteTrackTest {
@@ -4,7 +4,6 @@ import static androidx.test.espresso.Espresso.onData;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.longClick;
import static androidx.test.espresso.action.ViewActions.scrollTo;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isClickable;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
@@ -15,6 +15,10 @@
*/
package de.dennisguse.opentracks.chart;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -25,10 +29,6 @@ import org.junit.runner.RunWith;
import de.dennisguse.opentracks.R;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Tests {@link ChartValueSeries}.
*
@@ -142,7 +142,7 @@ public class EGM2008UtilsTest {
}
@Test
public void data_Berlin() throws IOException {
public void data_Berlin_Germany_() throws IOException {
// given
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
trackPoint.setLatitude(52.530644);
@@ -157,7 +157,7 @@ public class EGM2008UtilsTest {
}
@Test
public void data_Berlin_Caching() throws IOException {
public void data_Berlin_Germany_Caching() throws IOException {
// given
TrackPoint trackPoint1 = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
trackPoint1.setLatitude(52.530644);
@@ -176,6 +176,22 @@ public class EGM2008UtilsTest {
assertNotEquals(altitude_egm2008.correctAltitude(trackPoint1.getLocation()), altitude_egm2008.correctAltitude(trackPoint2.getLocation()), 0.0001);
}
@Test
public void data_Seattle_USA() throws IOException {
// given
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
trackPoint.setLatitude(47.63153);
trackPoint.setLongitude(-122.30938);
trackPoint.setAltitude(0);
// when
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
// then
assertEquals(22.99, altitude_egm2008.correctAltitude(trackPoint.getLocation()), MAX_BILINEAR_ERROR);
}
@Test
public void data_MaxUndulation() throws IOException {
// given
@@ -191,6 +207,7 @@ public class EGM2008UtilsTest {
assertEquals(-85.824, altitude_egm2008.correctAltitude(trackPoint.getLocation()), MAX_BILINEAR_ERROR);
}
@Test
public void getIndices() {
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
@@ -208,7 +225,7 @@ public class EGM2008UtilsTest {
trackPoint.setLatitude(-90);
trackPoint.setLongitude(-180);
assertEquals(new EGM2008Utils.Indices(2160, 0), EGM2008Utils.getIndices(trackPoint.getLocation()));
assertEquals(new EGM2008Utils.Indices(2160, 2160), EGM2008Utils.getIndices(trackPoint.getLocation()));
}
@Test
+2
View File
@@ -59,6 +59,8 @@ limitations under the License.
tools:targetApi="s" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application
android:name=".Startup"
android:allowBackup="false"
@@ -84,8 +84,8 @@ public abstract class AbstractTrackDeleteActivity extends AbstractActivity imple
/**
* Called every time a track is deleted.
*/
protected void onTrackDeleteStatus(TrackDeleteService.DeleteStatus deleteStatus) {
if (deleteStatus.isFinished() && trackDeleteServiceConnection != null) {
protected void onTrackDeleteStatus(TrackDeleteService.DeletionFinishedStatus deletionFinishedStatus) {
if (trackDeleteServiceConnection != null) {
trackDeleteServiceConnection.unbind(this);
trackDeleteServiceConnection = null;
onDeleteFinished();
@@ -3,19 +3,15 @@ package de.dennisguse.opentracks;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import de.dennisguse.opentracks.databinding.ActivityShowErrorBinding;
import de.dennisguse.opentracks.databinding.TrackListBinding;
public class ShowErrorActivity extends AbstractActivity {
@@ -70,7 +70,7 @@ import de.dennisguse.opentracks.ui.util.ActivityUtils;
import de.dennisguse.opentracks.ui.util.ListItemUtils;
import de.dennisguse.opentracks.util.IntentDashboardUtils;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.PermissionUtils;
import de.dennisguse.opentracks.util.PermissionRequester;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
@@ -241,7 +241,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
startActivity(newIntent);
connection.unbind(this);
}).startAndBind(this);
}).startAndBind(this, true);
});
viewBinding.trackListFabAction.setOnLongClickListener((view) -> {
if (!recordingStatus.isRecording()) {
@@ -263,13 +263,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
}
private void requestRequiredPermissions() {
if (!PermissionUtils.hasGPSPermission(this)) {
PermissionUtils.requestGPSPermission(this, null, () -> Toast.makeText(this, R.string.permission_gps_failed, Toast.LENGTH_SHORT).show());
}
if (!PermissionUtils.hasBluetoothPermissions(this)) {
PermissionUtils.requestBluetoothPermission(this, null, null);
}
PermissionRequester.ALL.requestPermissionsIfNeeded(this, this, null, (requester) -> Toast.makeText(this, R.string.permission_gps_failed, Toast.LENGTH_SHORT).show());
}
@Override
@@ -323,8 +317,8 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
searchView.findViewById(R.id.search_edit_frame).setPadding(0, 0, 48, 0);
SearchView.SearchAutoComplete searchAutoComplete = searchView.findViewById(R.id.search_src_text);
searchAutoComplete.setHintTextColor(getResources().getColor(android.R.color.white));
searchAutoComplete.setTextColor(getResources().getColor(android.R.color.white));
searchAutoComplete.setHintTextColor(ContextCompat.getColor(this, android.R.color.white));
searchAutoComplete.setTextColor(ContextCompat.getColor(this, android.R.color.white));
searchView.setOnCloseListener(() -> {
searchView.clearFocus();
@@ -213,7 +213,7 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
connection.unbind(this);
finish();
}).startAndBind(this);
}).startAndBind(this, true);
return true;
}
@@ -232,9 +232,9 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
}
@Override
protected void onTrackDeleteStatus(TrackDeleteService.DeleteStatus deleteStatus) {
super.onTrackDeleteStatus(deleteStatus);
if (deleteStatus.isDeleted(trackId)) {
protected void onTrackDeleteStatus(TrackDeleteService.DeletionFinishedStatus deletionFinishedStatus) {
super.onTrackDeleteStatus(deletionFinishedStatus);
if (deletionFinishedStatus.isDeleted(trackId)) {
runOnUiThread(this::finish);
}
}
@@ -11,8 +11,6 @@ import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowInsets;
import android.view.WindowInsetsController;
import android.view.WindowManager;
import android.widget.Toast;
@@ -49,7 +47,6 @@ import de.dennisguse.opentracks.ui.markers.MarkerEditActivity;
import de.dennisguse.opentracks.ui.markers.MarkerListActivity;
import de.dennisguse.opentracks.util.IntentDashboardUtils;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.PermissionUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.util.TrackUtils;
@@ -138,7 +135,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
viewBinding.trackRecordingFabAction.setImageResource(R.drawable.ic_baseline_stop_24);
viewBinding.trackRecordingFabAction.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.opentracks_secondary_color));
viewBinding.trackRecordingFabAction.setBackgroundColor(getResources().getColor(R.color.opentracks_secondary_color));
viewBinding.trackRecordingFabAction.setBackgroundColor(ContextCompat.getColor(this, R.color.opentracks_secondary_color));
viewBinding.trackRecordingFabAction.setOnLongClickListener((view) -> {
((Vibrator) getSystemService(Context.VIBRATOR_SERVICE)).vibrate(1000);
trackRecordingServiceConnection.stopRecording(TrackRecordingActivity.this);
@@ -145,7 +145,7 @@ public class TrackStoppedActivity extends AbstractTrackDeleteActivity implements
connection.unbind(this);
finish();
}).startAndBind(this);
}).startAndBind(this, true);
}
@Override
@@ -32,9 +32,7 @@ import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.TrackDataHub;
import de.dennisguse.opentracks.data.models.Altitude;
import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.databinding.ChartBinding;
@@ -3,7 +3,6 @@ package de.dennisguse.opentracks.chart;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.data.models.Altitude;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.TrackPoint;
@@ -24,6 +24,7 @@ import android.graphics.Paint.Style;
import android.graphics.Path;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import java.text.NumberFormat;
@@ -68,12 +69,12 @@ abstract class ChartValueSeries {
fillPaint = new Paint();
fillPaint.setStyle(Style.FILL);
fillPaint.setColor(context.getResources().getColor(fillColor));
fillPaint.setColor(ContextCompat.getColor(context, fillColor));
fillPaint.setAntiAlias(true);
strokePaint = new Paint();
strokePaint.setStyle(Style.STROKE);
strokePaint.setColor(context.getResources().getColor(strokeColor));
strokePaint.setColor(ContextCompat.getColor(context, strokeColor));
strokePaint.setAntiAlias(true);
// Make copies of the stroke paint with the default thickness
@@ -187,7 +187,6 @@ public class ContentProviderUtils {
FileUtils.deleteDirectoryRecurse(FileUtils.getPhotoDir(context, trackId));
}
// Delete track last since it triggers a database vacuum call
String whereClause = String.format(TracksColumns._ID + " IN (%s)", TextUtils.join(",", Collections.nCopies(trackIds.size(), "?")));
contentResolver.delete(TracksColumns.CONTENT_URI, whereClause, trackIds.stream().map(id -> Long.toString(id.getId())).toArray(String[]::new));
}
@@ -195,8 +194,6 @@ public class ContentProviderUtils {
public void deleteTrack(Context context, @NonNull Track.Id trackId) {
// Delete track folder resources.
FileUtils.deleteDirectoryRecurse(FileUtils.getPhotoDir(context, trackId));
// Delete track last since it triggers a database vacuum call
contentResolver.delete(TracksColumns.CONTENT_URI, TracksColumns._ID + "=?", new String[]{Long.toString(trackId.getId())});
}
@@ -27,6 +27,7 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
@@ -39,6 +40,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.settings.PreferencesUtils;
/**
* A {@link ContentProvider} that handles access to track points, tracks, and markers tables.
@@ -53,6 +55,8 @@ public class CustomContentProvider extends ContentProvider {
private static final String SQL_LIST_DELIMITER = ",";
private static final int TOTAL_DELETED_ROWS_VACUUM_THRESHOLD = 10000;
private final UriMatcher uriMatcher;
private SQLiteDatabase db;
@@ -131,14 +135,12 @@ public class CustomContentProvider extends ContentProvider {
@Override
public int delete(@NonNull Uri url, String where, String[] selectionArgs) {
String table;
boolean shouldVacuum = false;
switch (getUrlType(url)) {
case TRACKPOINTS:
table = TrackPointsColumns.TABLE_NAME;
break;
case TRACKS:
table = TracksColumns.TABLE_NAME;
shouldVacuum = true;
break;
case MARKERS:
table = MarkerColumns.TABLE_NAME;
@@ -147,23 +149,40 @@ public class CustomContentProvider extends ContentProvider {
throw new IllegalArgumentException("Unknown URL " + url);
}
Log.w(TAG, "Deleting table " + table);
int count;
Log.w(TAG, "Deleting from table " + table);
int totalChangesBefore = getTotalChanges();
int deletedRowsFromTable;
try {
db.beginTransaction();
count = db.delete(table, where, selectionArgs);
deletedRowsFromTable = db.delete(table, where, selectionArgs);
Log.i(TAG, "Deleted " + deletedRowsFromTable + " rows of table " + table);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
getContext().getContentResolver().notifyChange(url, null, false);
if (shouldVacuum) {
// If a potentially large amount of data was deleted, reclaim its space.
Log.i(TAG, "Vacuuming the database.");
int totalChanges = getTotalChanges() - totalChangesBefore;
Log.i(TAG, "Deleted " + totalChanges + " total rows from database");
PreferencesUtils.addTotalRowsDeleted(totalChanges);
int totalRowsDeleted = PreferencesUtils.getTotalRowsDeleted();
if (totalRowsDeleted > TOTAL_DELETED_ROWS_VACUUM_THRESHOLD) {
Log.i(TAG, "TotalRowsDeleted " + totalRowsDeleted + ", starting to vacuum the database.");
db.execSQL("VACUUM");
PreferencesUtils.resetTotalRowsDeleted();
}
return count;
return deletedRowsFromTable;
}
private int getTotalChanges() {
int totalCount;
try (Cursor cursor = db.rawQuery("SELECT total_changes()", null)) {
cursor.moveToNext();
totalCount = cursor.getInt(0);
}
return totalCount;
}
@Override
@@ -32,7 +32,6 @@ import java.util.HashSet;
import java.util.Set;
import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.data.tables.MarkerColumns;
@@ -17,8 +17,8 @@ import androidx.documentfile.provider.DocumentFile;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.io.file.exporter.ExportActivity;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.util.IntentUtils;
public abstract class DirectoryChooserActivity extends AppCompatActivity {
@@ -25,6 +25,7 @@ import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
import de.dennisguse.opentracks.ui.markers.MarkerUtils;
@@ -290,4 +291,5 @@ public class TrackImporter {
public void cleanImport() {
contentProviderUtils.deleteTracks(context, trackIds);
}
}
@@ -34,7 +34,7 @@ public abstract class AbstractAPIActivity extends AppCompatActivity {
if (PreferencesUtils.isPublicAPIenabled()) {
Log.i(TAG, "Received and trying to execute requested action.");
new TrackRecordingServiceConnection(serviceConnectedCallback)
.startAndBind(this);
.startAndBind(this, isStartServiceForeground());
} else {
Toast.makeText(this, getString(R.string.settings_public_api_disabled_toast), Toast.LENGTH_LONG).show();
Log.w(TAG, "Public API is disabled; ignoring request.");
@@ -42,6 +42,10 @@ public abstract class AbstractAPIActivity extends AppCompatActivity {
}
}
protected boolean isStartServiceForeground() {
return false;
}
protected abstract void execute(TrackRecordingService service);
protected abstract boolean isPostExecuteStopService();
@@ -57,4 +57,9 @@ public class StartRecording extends AbstractAPIActivity {
protected boolean isPostExecuteStopService() {
return false;
}
@Override
protected boolean isStartServiceForeground() {
return true;
}
}
@@ -35,7 +35,7 @@ import de.dennisguse.opentracks.sensors.sensorData.SensorDataCycling;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataRunning;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.util.PermissionUtils;
import de.dennisguse.opentracks.util.PermissionRequester;
/**
* Bluetooth LE sensor manager: manages connections to Bluetooth LE sensors.
@@ -162,7 +162,7 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
} else {
connectionManager.disconnect();
}
if (!PermissionUtils.hasBluetoothPermissions(context)) {
if (!PermissionRequester.BLUETOOTH.hasPermission(context)) {
Log.w(TAG, "BLUETOOTH_SCAN and/or BLUETOOTH_CONNECT not granted; not connecting.");
}
@@ -15,12 +15,14 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.settings.PreferencesUtils;
public class TrackDeleteService extends Service {
@@ -31,7 +33,7 @@ public class TrackDeleteService extends Service {
private final Binder binder = new Binder();
private ExecutorService serviceExecutor;
private MutableLiveData<DeleteStatus> deleteResultObservable;
private MutableLiveData<DeletionFinishedStatus> deleteResultObservable;
private NotificationManager notificationManager;
private NotificationCompat.Builder notificationBuilder;
@@ -59,22 +61,18 @@ public class TrackDeleteService extends Service {
return START_NOT_STICKY;
}
private void deleteTracks(@NonNull ArrayList<Track.Id> trackIds) {
sendResult(null,0, trackIds.size());
private void deleteTracks(@NonNull List<Track.Id> trackIds) {
serviceExecutor.execute(() -> {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this);
for (int i = 0; i < trackIds.size(); i++) {
updateNotification(i + 1, trackIds.size());
contentProviderUtils.deleteTrack(this, trackIds.get(i));
sendResult(trackIds.get(i), i + 1, trackIds.size());
}
contentProviderUtils.deleteTracks(this, trackIds);
sendResult(trackIds);
stopSelf();
});
}
private void sendResult(Track.Id trackId, int deletes, int total) {
private void sendResult(List<Track.Id> trackIds) {
if (deleteResultObservable != null) {
deleteResultObservable.postValue(new DeleteStatus(trackId, deletes, total));
deleteResultObservable.postValue(new DeletionFinishedStatus(trackIds));
}
}
@@ -83,7 +81,7 @@ public class TrackDeleteService extends Service {
return binder;
}
public LiveData<DeleteStatus> getDeletingStatusObservable() {
public LiveData<DeletionFinishedStatus> getDeletingStatusObservable() {
return deleteResultObservable;
}
@@ -106,25 +104,13 @@ public class TrackDeleteService extends Service {
notificationBuilder = new NotificationCompat.Builder(this, CHANNEL_ID);
notificationBuilder
.setContentTitle(this.getString(R.string.track_delete_progress_message))
.setContentText(this.getString(R.string.track_delete_progress, 0, tracksToDelete))
.setContentText(this.getString(R.string.track_delete_number_of_tracks, tracksToDelete))
.setSmallIcon(R.drawable.ic_logo_color_24dp)
.setProgress(tracksToDelete, 0, false);
.setProgress(0, 0, true);
startForeground(NOTIFICATION_ID, notificationBuilder.build());
}
/**
* Updates notification progress.
*
* @param progress number of tracks already deleted.
* @param total total of tracks to be deleted.
*/
private void updateNotification(int progress, int total) {
notificationBuilder.setProgress(total, progress, false);
notificationBuilder.setContentText(this.getString(R.string.track_delete_progress, progress, total));
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
}
public class Binder extends android.os.Binder {
private Binder() {
@@ -136,36 +122,25 @@ public class TrackDeleteService extends Service {
}
}
public static class DeleteStatus {
private final int progress;
private final int max;
private final Track.Id trackId;
public static class DeletionFinishedStatus {
private final List<Track.Id> trackIds;
/**
* @param trackId Track.Id just deleted if any.
* @param progress number of deletes.
* @param max total of deletes to be done.
* @param trackIds List of deleted Track.Ids.
*/
private DeleteStatus(@Nullable Track.Id trackId, int progress, int max) {
this.trackId = trackId;
this.progress = progress;
this.max = max;
}
public boolean isFinished() {
return progress == max;
private DeletionFinishedStatus(@Nullable List<Track.Id> trackIds) {
this.trackIds = trackIds;
}
public boolean isDeleted(Track.Id trackId) {
return this.trackId != null && this.trackId.equals(trackId);
return this.trackIds != null && this.trackIds.contains(trackId);
}
@NonNull
@Override
public String toString() {
return "DeleteStatus{" +
"number of deletes=" + progress +
", total=" + max +
"trackIds=" + trackIds +
'}';
}
}
@@ -129,7 +129,8 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
trackRecordingManager = null;
// Reverse order from onCreate
showNotification(false); //TODO Why?
stopForeground(true);
notificationManager.stop();
notificationManager = null;
@@ -205,7 +206,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
private void startSensors() {
wakeLock = SystemUtils.acquireWakeLock(this, wakeLock);
trackPointCreator.start(this, handler);
showNotification(true);
startForeground(TrackRecordingServiceNotificationManager.NOTIFICATION_ID, notificationManager.setGPSonlyStarted(this));
}
public void endCurrentTrack() {
@@ -247,7 +248,8 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
if (!isRecording()) return;
trackPointCreator.stop();
showNotification(false);
stopForeground(true);
notificationManager.cancelNotification();
wakeLock = SystemUtils.releaseWakeLock(wakeLock);
}
@@ -273,19 +275,6 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
}
}
private void showNotification(boolean isGpsStarted) {
if (isRecording()) {
startForeground(TrackRecordingServiceNotificationManager.NOTIFICATION_ID, notificationManager.setRecording(this, recordingStatus.getTrackId()));
}
if (!isRecording() && isGpsStarted) {
startForeground(TrackRecordingServiceNotificationManager.NOTIFICATION_ID, notificationManager.setGPSonlyStarted(this));
}
if (!isRecording() && !isGpsStarted) {
stopForeground(true);
notificationManager.cancelNotification();
}
}
public Marker.Id insertMarker(String name, String category, String description, String photoUrl) {
if (!isRecording()) {
return null;
@@ -28,6 +28,7 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import de.dennisguse.opentracks.BuildConfig;
import de.dennisguse.opentracks.R;
@@ -66,15 +67,21 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
/**
* Starts and binds the service.
*
* @param foreground is the service expected to call `startForeground()`?
*/
public void startAndBind(Context context) {
public void startAndBind(Context context, boolean foreground) {
if (trackRecordingService != null) {
// Service is already started and bound.
return;
}
Log.i(TAG, "Starting the service.");
context.startService(new Intent(context, TrackRecordingService.class));
if (foreground) {
ContextCompat.startForegroundService(context, new Intent(context, TrackRecordingService.class));
} else {
context.startService(new Intent(context, TrackRecordingService.class));
}
startConnection(context);
}
@@ -88,7 +95,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
@Deprecated
public void startAndBindWithCallback(Context context) {
if (trackRecordingService == null) {
startAndBind(context);
startAndBind(context, false);
return;
}
if (callback != null) {
@@ -68,6 +68,7 @@ class TrackRecordingServiceNotificationManager implements SharedPreferences.OnSh
}
void stop() {
cancelNotification();
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(this);
}
@@ -5,8 +5,8 @@ import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnno
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceAverageSpeedPace;
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceLapHeartRate;
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceLapSpeedPace;
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceTotalDistance;
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceMovingTime;
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceTotalDistance;
import android.content.Context;
import android.text.Spannable;
@@ -19,7 +19,7 @@ import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.util.LocationUtils;
import de.dennisguse.opentracks.util.PermissionUtils;
import de.dennisguse.opentracks.util.PermissionRequester;
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public class GPSHandler implements LocationListener, GpsStatus.GpsStatusListener, SharedPreferences.OnSharedPreferenceChangeListener {
@@ -54,7 +54,7 @@ public class GPSHandler implements LocationListener, GpsStatus.GpsStatusListener
//TODO upgrade to AGP7.0.0/API31 started complaining about removeUpdates.
public void onStop() {
if (locationManager != null && context != null) {
if (PermissionUtils.hasGPSPermission(context)) {
if (PermissionRequester.GPS.hasPermission(context)) {
locationManager.removeUpdates(this);
}
locationManager = null;
@@ -152,7 +152,7 @@ public class GPSHandler implements LocationListener, GpsStatus.GpsStatusListener
Log.e(TAG, "Not started.");
return;
}
if (PermissionUtils.hasGPSPermission(context)) {
if (PermissionRequester.GPS.hasPermission(context)) {
try {
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, gpsInterval.toMillis(), 0, this);
} catch (SecurityException e) {
@@ -23,7 +23,7 @@ class GpsStatus {
private static final String TAG = GpsStatus.class.getSimpleName();
// The duration that GpsStatus waits from minimal interval to consider GPS lost.
private static final Duration SIGNAL_LOST_THRESHOLD = Duration.ofSeconds(10);
private static final Duration SIGNAL_LOST_THRESHOLD = Duration.ofSeconds(30);
private Distance thresholdHorizontalAccuracy;
// Threshold for time without points.
@@ -8,9 +8,6 @@ import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.services.RecordingStatus;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
public class MainSettingsFragment extends PreferenceFragmentCompat {
@@ -826,4 +826,17 @@ public class PreferencesUtils {
return getString(R.string.show_on_map_format_key, IntentDashboardUtils.PREFERENCE_ID_ASK);
}
public static int getTotalRowsDeleted() {
return getInt(R.string.total_rows_deleted_key, 0);
}
public static void addTotalRowsDeleted(final int totalRowsDeletedToAdd) {
int newTotalRowsDeleted = getTotalRowsDeleted() + totalRowsDeletedToAdd;
setInt(R.string.total_rows_deleted_key, newTotalRowsDeleted);
}
public static void resetTotalRowsDeleted() {
setInt(R.string.total_rows_deleted_key, 0);
}
}
@@ -11,7 +11,6 @@ import androidx.preference.PreferenceFragmentCompat;
import de.dennisguse.opentracks.AbstractActivity;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.databinding.SettingsBinding;
import de.dennisguse.opentracks.databinding.TrackRecordedBinding;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
public class SettingsActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller {
@@ -29,7 +29,7 @@ import java.util.stream.Collectors;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.sensors.BluetoothUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.util.PermissionUtils;
import de.dennisguse.opentracks.util.PermissionRequester;
/**
* Preference to select a discoverable Bluetooth LE device.
@@ -145,20 +145,18 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
bluetoothIcon = AnimatedVectorDrawableCompat.create(getContext(), R.drawable.ic_bluetooth_searching_animated_24dp);
bluetoothIcon.start();
if (!PermissionUtils.hasBluetoothPermissions(getContext())) {
PermissionUtils.requestBluetoothPermission(this,
this::startBluetoothScan,
() -> {
if (PermissionUtils.shouldShowRequestPermissionRationaleBluetooth(this)) {
Toast.makeText(getContext(), R.string.permission_bluetooth_failed_rejected, Toast.LENGTH_LONG).show();
} else {
Toast.makeText(getContext(), R.string.permission_bluetooth_failed, Toast.LENGTH_SHORT).show();
}
dismiss();
});
} else {
startBluetoothScan();
}
PermissionRequester.BLUETOOTH.requestPermissionsIfNeeded(getContext(), this,
this::startBluetoothScan,
(requester) -> {
if (requester.shouldShowRequestPermissionRationale(this)) {
Toast.makeText(getContext(), R.string.permission_bluetooth_failed_rejected, Toast.LENGTH_LONG).show();
} else {
Toast.makeText(getContext(), R.string.permission_bluetooth_failed, Toast.LENGTH_SHORT).show();
}
dismiss();
});
startBluetoothScan();
}
private void startBluetoothScan() {
@@ -5,7 +5,6 @@ import android.content.Context;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.DatePicker;
@@ -22,6 +22,8 @@ import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.core.content.ContextCompat;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
@@ -169,7 +171,7 @@ public class ListItemUtils {
textView.setVisibility(View.VISIBLE);
textView.setText(value);
if (addShadow) {
textView.setShadowLayer(5, 0, 2, context.getResources().getColor(android.R.color.black));
textView.setShadowLayer(5, 0, 2, ContextCompat.getColor(context, android.R.color.black));
} else {
textView.setShadowLayer(0, 0, 0, 0);
}
@@ -27,11 +27,11 @@ abstract public class RecyclerViewSwipeDeleteCallback extends ItemTouchHelper.Ca
public RecyclerViewSwipeDeleteCallback(Context context) {
background = new ColorDrawable();
backgroundColor = context.getResources().getColor(R.color.colorBackgroundDelete);
backgroundColor = ContextCompat.getColor(context, R.color.colorBackgroundDelete);
clearPaint = new Paint();
clearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
deleteDrawable = ContextCompat.getDrawable(context, R.drawable.ic_delete_forever_24dp);
deleteDrawable.setTint(context.getResources().getColor(R.color.colorForegroundDelete));
deleteDrawable.setTint(ContextCompat.getColor(context, R.color.colorForegroundDelete));
intrinsicWidth = deleteDrawable.getIntrinsicWidth();
intrinsicHeight = deleteDrawable.getIntrinsicHeight();
}
@@ -65,7 +65,7 @@ public class EGM2008Utils {
if (location.getLongitude() >= 0) {
longitude = location.getLongitude();
} else {
longitude = 180 + Math.abs(location.getLongitude());
longitude = 360 + location.getLongitude();
}
int longitudeIndex = (int) (longitude * RESOLUTION_IN_MINUTES);
@@ -172,5 +172,13 @@ public class EGM2008Utils {
public int hashCode() {
return Objects.hash(latitudeIndex, longitudeIndex);
}
@Override
public String toString() {
return "Indices{" +
"latitudeIndex=" + latitudeIndex +
", longitudeIndex=" + longitudeIndex +
'}';
}
}
}
@@ -17,8 +17,6 @@ import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.regex.Pattern;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.Track;
@@ -0,0 +1,101 @@
package de.dennisguse.opentracks.util;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import androidx.activity.result.ActivityResultCaller;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class PermissionRequester {
private final List<String> permissions;
public PermissionRequester(List<String> permissions) {
this.permissions = permissions;
}
public boolean hasPermission(Context context) {
return permissions.stream()
.map(p -> ContextCompat.checkSelfPermission(context, p))
.allMatch(r -> r == PackageManager.PERMISSION_GRANTED);
}
public void requestPermissionsIfNeeded(Context context, ActivityResultCaller caller, @Nullable Runnable onGranted, @Nullable RejectedCallback onRejected) {
if (!hasPermission(context)) {
requestPermission(caller, onGranted, onRejected);
}
}
public boolean shouldShowRequestPermissionRationale(Fragment context) {
return permissions.stream()
.anyMatch(context::shouldShowRequestPermissionRationale);
}
private void requestPermission(ActivityResultCaller context, @Nullable Runnable onGranted, @Nullable RejectedCallback onRejected) {
ActivityResultLauncher<String[]> locationPermissionRequest = context.registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), result -> {
boolean isGranted = permissions.stream()
.allMatch(p -> result.getOrDefault(p, false));
if (isGranted && onGranted != null) {
onGranted.run();
}
if (!isGranted && onRejected != null) {
onRejected.rejected(this);
}
}
);
locationPermissionRequest.launch(permissions.toArray(permissions.toArray(new String[0])));
}
private static final List<String> GPS_PERMISSION = List.of(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION);
private static final List<String> BLUETOOTH_PERMISSIONS;
static {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
BLUETOOTH_PERMISSIONS = List.of(Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT);
} else {
BLUETOOTH_PERMISSIONS = Collections.emptyList();
}
}
private static final List<String> NOTIFICATION_PERMISSIONS;
static {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
NOTIFICATION_PERMISSIONS = List.of(Manifest.permission.POST_NOTIFICATIONS);
} else {
NOTIFICATION_PERMISSIONS = Collections.emptyList();
}
}
private static final List<String> ALL_PERMISSIONS;
static {
ArrayList<String> all = new ArrayList<>(GPS_PERMISSION);
all.addAll(BLUETOOTH_PERMISSIONS);
all.addAll(NOTIFICATION_PERMISSIONS);
ALL_PERMISSIONS = Collections.unmodifiableList(all);
}
public final static PermissionRequester GPS = new PermissionRequester(GPS_PERMISSION);
public final static PermissionRequester BLUETOOTH = new PermissionRequester(BLUETOOTH_PERMISSIONS);
public final static PermissionRequester NOTIFICATION = new PermissionRequester(NOTIFICATION_PERMISSIONS);
public final static PermissionRequester ALL = new PermissionRequester(ALL_PERMISSIONS);
public interface RejectedCallback {
void rejected(PermissionRequester permissionRequester);
}
}
@@ -1,74 +0,0 @@
package de.dennisguse.opentracks.util;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import androidx.activity.result.ActivityResultCaller;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import java.util.Arrays;
public class PermissionUtils {
private static final String[] GPS = new String[]{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION};
private static final String[] BLUETOOTH;
static {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
BLUETOOTH = new String[]{};
} else {
BLUETOOTH = new String[]{Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT};
}
}
private PermissionUtils() {
}
public static boolean hasGPSPermission(Context context) {
return hasPermissions(context, GPS);
}
public static void requestGPSPermission(ActivityResultCaller context, @Nullable Runnable onGranted, @Nullable Runnable onRejected) {
requestPermission(context, GPS, onGranted, onRejected);
}
public static boolean shouldShowRequestPermissionRationaleBluetooth(Fragment context) {
return Arrays.stream(BLUETOOTH).anyMatch(context::shouldShowRequestPermissionRationale);
}
public static boolean hasBluetoothPermissions(Context context) {
return hasPermissions(context, BLUETOOTH);
}
public static void requestBluetoothPermission(ActivityResultCaller context, @Nullable Runnable onGranted, @Nullable Runnable onRejected) {
requestPermission(context, BLUETOOTH, onGranted, onRejected);
}
private static void requestPermission(ActivityResultCaller context, final String[] permissions, @Nullable Runnable onGranted, @Nullable Runnable onRejected) {
ActivityResultLauncher<String[]> locationPermissionRequest = context.registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), result -> {
boolean isGranted = Arrays.stream(permissions)
.allMatch(p -> result.getOrDefault(p, false));
if (isGranted && onGranted != null) {
onGranted.run();
}
if (!isGranted && onRejected != null) {
onRejected.run();
}
}
);
locationPermissionRequest.launch(permissions);
}
private static boolean hasPermissions(Context context, String[] permissions) {
return Arrays.stream(permissions)
.map(p -> ContextCompat.checkSelfPermission(context, p))
.allMatch(r -> r == PackageManager.PERMISSION_GRANTED);
}
}
+8 -7
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!--
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,8 +13,7 @@ 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.
-->
<resources>
--><resources>
<!-- Activity Type -->
<string name="activity_type_airplane">طائرة</string>
<string name="activity_type_atv">ATV</string>
@@ -200,14 +200,15 @@ limitations under the License.
<string name="settings_stats_units_imperial">وحدات القياس البريطانية (ميل، قدم)</string>
<string name="settings_stats_units_metric">وحدات القياس المترية (كم، م)</string>
<string name="settings_stats_units_title">الوحدات المفضلة</string>
<!-- Share Track -->
<string name="share_track_share_file_body">أعتقد أنك قد تكون مهتمًا بهذا المسار.\n\nلعرض هذا المسار، استخدام برنامج Google Earth أو غيره من البرامج تصميم الخرائط.\n\n%1$s</string>
<string name="share_track_share_file_body">أعتقد أنك قد تكون مهتمًا بهذا المسار.
\n
\n%1$s</string>
<string name="share_track_subject">أريد مشاركة مسار معك</string>
<!-- Stats -->
<string name="stats_average_moving_pace">متوسط إيقاع التحرك</string>
<string name="stats_average_moving_speed">متوسط سرعة التحرك</string>
<string name="stats_average_pace">متوسط ​​الإيقاع</string>
<string name="stats_average_pace">متوسط الإيقاع</string>
<string name="stats_average_speed">متوسط السرعة</string>
<string name="stats_altitude">الارتفاع</string>
<string name="stats_fastest_pace">أسرع إيقاع</string>
@@ -352,4 +353,4 @@ limitations under the License.
<string name="waypoint_type_waterfall">شلال</string>
<string name="waypoint_type_water_fountain">نافورة مياه</string>
<string name="waypoint_type_waypoint">نقطة طريق</string>
</resources>
</resources>
-1
View File
@@ -411,7 +411,6 @@
<string name="stats_split_interval">Intervalo partido</string>
<string name="track_recording_notification_accuracy">Precisión de ubicación: %1$s</string>
<string name="track_pace_notification">Ritmo: %1$s</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="track_detail_intervals_tab">Intervalos</string>
<string name="interval_list_empty_message">Aún no hay ningún intervalo</string>
<string name="settings_recording_customize_layout_title">Personalice su pantalla de grabación</string>
-1
View File
@@ -458,7 +458,6 @@ limitations under the License.
<string name="settings_recording_show_on_lockscreen_while_recording_title">Показване на статистически данни на заключения екран</string>
<string name="help_data_google_play_services_title">Изисква ли OpenTracks/използва ли услугата Google Play\?</string>
<string name="about_version_code">Код на версията: %1$d</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="export_error_post_workout">Експортът след тренировка е неуспешен, моля, проверете директорията за експорт.</string>
<string name="about_third_party_data">Данни от трети страни</string>
<string name="import_unsupported_format">Неподдържан формат на файла</string>
-1
View File
@@ -469,7 +469,6 @@ limitations under the License.
<string name="track_speed_notification">Rychlost: %1$s</string>
<string name="track_pace_notification">Rychlost: %1$s</string>
<string name="about_third_party_data">Údaje třetích stran</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="delete_service">Služba mazání</string>
<string name="menu_voice_rate">Rychlost hlasových oznámení</string>
<string name="track_delete_not_recording">Není možno vymazat aktuálně zaznamenávanou trasu.</string>
-1
View File
@@ -467,7 +467,6 @@ Hvis GPS-enheden rapporterer unøjagtige data (f.eks. Placering, hastighed, høj
<string name="track_speed_notification">Hastighed: %1$s</string>
<string name="about_third_party_data">Data fra tredjeparter</string>
<string name="delete_service">Tjeneste til sletning</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="menu_voice_rate">Stemmehastighed</string>
<string name="track_delete_not_recording">Dette spor kan ikke slettes, da det er optaget i øjeblikket.</string>
<string name="settings_sensor_cycling_distance_speed">Afstand og hastighed</string>
+3 -5
View File
@@ -276,9 +276,8 @@ Meldet das GPS-Gerät ungenaue Daten (z.B. Standort, Geschwindigkeit, Höhe), ka
\n%1$s</string>
<string name="share_track_subject">Ich möchte einen Track mit dir teilen</string>
<!-- Stats -->
<string name="stats_average_moving_pace">Durchschnitt\n(Bewegung)</string>
<string name="stats_average_moving_speed">Durchschnittsgeschwindigkeit
\n(Bewegung)</string>
<string name="stats_average_moving_pace">Durchschnittliches Bewegungstempo</string>
<string name="stats_average_moving_speed">Mittlere Geschwindigkeit in Bewegung</string>
<string name="stats_average_pace">Durchschnitt</string>
<string name="stats_average_speed">Durchschnittsgeschwindigkeit</string>
<string name="stats_altitude">Höhe</string>
@@ -464,13 +463,12 @@ Meldet das GPS-Gerät ungenaue Daten (z.B. Standort, Geschwindigkeit, Höhe), ka
<string name="about_third_party_data">Daten von Drittherstellern</string>
<string name="sensor_state_heart_rate_max">Maximaler Herzfrequenz</string>
<string name="track_delete_not_recording">Dieser Track kann nicht gelöscht werden, da er gerade aufgezeichnet wird.</string>
<string name="interval_list_empty_message">Es gibt noch keine Intervalle.</string>
<string name="interval_list_empty_message">Es gibt noch keine Intervalle</string>
<string name="import_parser_error">Parser Fehler: %1$s</string>
<string name="settings_recording_fullscreen_on_while_recording_summary">Anzeige im Vollbildmodus während der Aufzeichnung.</string>
<string name="sensor_state_heart_rate_avg">Durchschnittlicher Herzfrequenz</string>
<string name="settings_prevent_reimport_tracks_title">Verhindere den erneuten Import von Strecken(aufzeichnungen)</string>
<string name="import_no_kml_file_found">Keine doc.kml Datei im KMZ gefunden</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="import_thread_interrupted">Thread unterbrochen</string>
<string name="import_unable_to_import_file">Importieren der Datei gescheitert: %1$s</string>
<string name="delete_service">Löschdienst</string>
-1
View File
@@ -464,7 +464,6 @@ limitations under the License.
<string name="track_pace_notification">Ritmo: %1$s</string>
<string name="about_third_party_data">Datos de terceros</string>
<string name="delete_service">Borrar servicio</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="menu_voice_rate">Velocidad de la voz</string>
<string name="track_delete_not_recording">Este recorrido no puede ser eliminado porque se está grabando actualmente.</string>
<string name="settings_sensor_running_speed_and_cadence">Distancia, velocidad y cadencia</string>
-1
View File
@@ -464,7 +464,6 @@ limitations under the License.
<string name="track_pace_notification">Tempo: %1$s</string>
<string name="about_third_party_data">Kolmanda osapoole andmed</string>
<string name="delete_service">Kustutamise teenus</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="menu_voice_rate">Hääle kiirus</string>
<string name="track_delete_not_recording">Seda pala ei saa kustutada, kuna see on praegu salvestatud.</string>
<string name="settings_sensor_cycling_cadence">Rütm</string>
-1
View File
@@ -467,7 +467,6 @@ GPS gailuak datu okerrak salatzen baditu (adibidez, kokapena, abiadura, kota), O
<string name="track_pace_notification">Erritmoa: %1$s</string>
<string name="about_third_party_data">Hirugarrenen datuak</string>
<string name="delete_service">Ezabatzeko zerbitzua</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="menu_voice_rate">Ahots abiadura</string>
<string name="track_delete_not_recording">Pista hau ezin da ezabatu da gaur egun grabatzen.</string>
<string name="settings_sensor_cycling_cadence">Kadentzia</string>
-1
View File
@@ -469,7 +469,6 @@ limitations under the License.
<string name="track_speed_notification">Nopeus: %1$s</string>
<string name="about_third_party_data">Kolmannen osapuolen tiedot</string>
<string name="delete_service">Poistopalvelu</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="menu_voice_rate">Äänen nopeus</string>
<string name="track_delete_not_recording">Tätä reittiä ei voi poistaa koska sitä tallennetaan parhaillaan.</string>
<string name="settings_sensor_cycling_cadence">Pyöräily: tahtiaika</string>
-1
View File
@@ -465,7 +465,6 @@ Si le dispositif GPS signale des données inexactes (par exemple, la localisatio
<string name="export_track_already_exists_msg">La trace %1$s existe déjà dans le répertoire de destination.</string>
<string name="export_progress_summary_overwrite_msg">Traces écrasées</string>
<string name="about_third_party_data">Données de tiers</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="delete_service">Service de suppression</string>
<string name="menu_voice_rate">Vitesse vocale</string>
<string name="track_delete_not_recording">Cette piste ne peut pas être supprimée car elle est en cours d\'enregistrement.</string>
-1
View File
@@ -472,7 +472,6 @@ Se te detés ou estás en interiores, non se gardarán datos do sensor (mais ser
<string name="track_speed_notification">Velocidade: %1$s</string>
<string name="about_third_party_data">Datos de terceiros alleos</string>
<string name="delete_service">Eliminación de servizo</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="menu_voice_rate">Velocidade da voz</string>
<string name="track_delete_not_recording">Esta pista non se pode eliminar porque está a funcionar a gravación.</string>
<string name="settings_sensor_cycling_distance_speed">Distancia e Velocidade</string>
-1
View File
@@ -460,7 +460,6 @@ limitations under the License.
\n * pályák megjelenítése térképen (lásd alább)</string>
<string name="about_third_party_data">Harmadik féltől származó adatok</string>
<string name="delete_service">Törlési szolgáltatás</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="menu_voice_rate">Hang sebesség</string>
<string name="track_delete_not_recording">Ez a sáv nem törölhető, mivel jelenleg is rögzítve van.</string>
<string name="settings_sensor_cycling_cadence">Kerékpározás: lépésszám</string>
-1
View File
@@ -471,7 +471,6 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
<string name="track_pace_notification">Ritmo: %1$s</string>
<string name="about_third_party_data">Dati di terzi</string>
<string name="delete_service">Servizio di eliminazione</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="menu_voice_rate">Velocità della voce</string>
<string name="track_delete_not_recording">Questa traccia non può essere cancellata perché è attualmente in registrazione.</string>
<string name="settings_sensor_cycling_cadence">Cadenza</string>
-1
View File
@@ -416,7 +416,6 @@ limitations under the License.
<string name="help_recording_content">OpenTracks naudoja prietaiso GPS jutiklį, kad nuolat įrašytų vietą. Jei GPS įrenginys praneša apie netikslius duomenis (pvz., Vietą, greitį, aukštį), OpenTracks negali nieko padaryti.</string>
<string name="generic_open_track">Atidaryti takelį</string>
<string name="settings_recording_keep_screen_on_while_recording_summary">Įrašydami palaikykite ekraną įjungtą.</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="open_track_as_GPX">Atidaryti kaip GPX</string>
<string name="help_gps_troubleshooting_title">OpenTracks nefiksuoja vietų\?</string>
<string name="generic_overwrite">Perrašyti</string>
-1
View File
@@ -416,7 +416,6 @@ limitations under the License.
<string name="help_track_export_title">Kā es varu eksportēt/ kopīgot ierakstītos ierakstus\?</string>
<string name="instant_export_enabled_summary">Dziesmas eksportēšana uz krātuvi pēc ierakstīšanas pabeigšanas</string>
<string name="activity_type_escooter">eScooter</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="sensor_state_power_avg">Vidējā jauda</string>
<string name="about_libraries">Bibliotēkas</string>
<string name="description_default_speed_or_pace">Pēc aktivitātes veida</string>
-1
View File
@@ -460,7 +460,6 @@ limitations under the License.
<string name="track_pace_notification">Tempo: %1$s</string>
<string name="about_third_party_data">Tredjepartsdata</string>
<string name="delete_service">Slettingstjeneste</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="menu_voice_rate">Talehastighet</string>
<string name="track_delete_not_recording">Dette sporet kan ikke slettes da det tas opp nå.</string>
<string name="settings_sensor_cycling_cadence">Kadens</string>
-1
View File
@@ -465,7 +465,6 @@ Als het GPS-apparaat onnauwkeurige gegevens rapporteert (bijv. locatie, snelheid
<string name="track_distance_notification">Afstand: %1$s</string>
<string name="track_pace_notification">Tempo: %1$s</string>
<string name="about_third_party_data">Gegevens van derden</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="delete_service">Dienst verwijdering</string>
<string name="menu_voice_rate">Spraak snelheid</string>
<string name="track_delete_not_recording">Dit fragment kan niet worden gewist omdat het momenteel is opgenomen.</string>
-1
View File
@@ -476,7 +476,6 @@ limitations under the License.
<string name="track_distance_notification">Odległość: %1$s</string>
<string name="about_third_party_data">Dane osób trzecich</string>
<string name="delete_service">Usługa usuwania</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="menu_voice_rate">Szybkość głosu</string>
<string name="track_delete_not_recording">Tej ścieżki nie można usunąć, ponieważ jest ona aktualnie nagrywana.</string>
<string name="settings_sensor_cycling_distance_speed">Dystans i Prędkość</string>
-1
View File
@@ -404,7 +404,6 @@
<string name="track_pace_notification">Parcela: %1$s</string>
<string name="about_third_party_data">Dados de terceiros</string>
<string name="delete_service">Serviço de exclusão</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="menu_voice_rate">Velocidade da voz</string>
<string name="track_delete_not_recording">Esta trilha não pode ser excluída porque está gravada no momento.</string>
<plurals name="voiceSpeedKilometersPerHour">
-1
View File
@@ -426,7 +426,6 @@ limitations under the License.
<string name="import_progress_summary_exists_msg">Există deja</string>
<string name="track_detail_intervals_tab">Intervaluri</string>
<string name="track_recording_notification_accuracy">Precizia locației: %1$s</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="sensor_state_cadence_max">Max Cadență</string>
<string name="sensor_could_not_scan">Nu a fost posibilă scanarea pentru dispozitive Bluetooth (eroare %1$i).</string>
<string name="share_image_subject">Aș dori să vă împărtășesc o imagine</string>
+66 -38
View File
@@ -24,7 +24,7 @@ limitations under the License.
<string name="activity_type_commercial_airplane">Пассажирские самолеты</string>
<string name="activity_type_cross_country_skiing">лыжные гонки</string>
<string name="activity_type_cycling">Езда на велосипеде</string>
<string name="activity_type_dirt_bike">Езда на велосипеде по бездорожью</string>
<string name="activity_type_dirt_bike">Дертбайк</string>
<string name="activity_type_donkey_back_riding">Верховая езда на ослах</string>
<string name="activity_type_driving">на автомобиле</string>
<string name="activity_type_driving_bus">Вождение автобуса</string>
@@ -54,7 +54,7 @@ limitations under the License.
<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_road_biking">Шоссейный велосипед</string>
<string name="activity_type_roller_skiing">роликовые лыжи</string>
<string name="activity_type_rowing">Гребля академическая</string>
<string name="activity_type_running">Бег</string>
@@ -128,7 +128,7 @@ limitations under the License.
<string name="image_arrow">Стрелка</string>
<string name="image_marker">Маркер</string>
<string name="image_marker_count">Количество маркеров</string>
<string name="image_photo_marker">Маркер фотографии</string>
<string name="image_photo_marker">Фотомаркер</string>
<string name="image_record">Запись</string>
<string name="image_stop">Стоп</string>
<string name="image_track">Трек</string>
@@ -212,7 +212,7 @@ limitations under the License.
<string name="stats_average_speed">Средняя скорость</string>
<string name="stats_altitude">Высота</string>
<string name="stats_fastest_pace">Макс. темп</string>
<string name="stats_gain">Рост</string>
<string name="stats_gain">Набор высоты</string>
<string name="stats_latitude">Широта</string>
<string name="stats_longitude">Долгота</string>
<string name="stats_max_speed">Макс. скорость</string>
@@ -337,53 +337,53 @@ limitations under the License.
<string name="waypoint_type_telephone">Телефон</string>
<string name="waypoint_type_theater">Театр</string>
<string name="waypoint_type_toilet">Туалет</string>
<string name="waypoint_type_tourist_information">Туристическая информация</string>
<string name="waypoint_type_tourist_information">Информация для туристов</string>
<string name="waypoint_type_tree">Дерево</string>
<string name="waypoint_type_tunnel">Тоннель</string>
<string name="waypoint_type_waterfall">Водопад</string>
<string name="waypoint_type_water_fountain">Фонтан</string>
<string name="waypoint_type_waypoint">путевая точка</string>
<string name="help_track_map_content"> Используйте значок маленькой карты.
<string name="help_track_map_content"> Используйте маленький значок карты.
\na) (живые обновления) Установите <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Он предоставляет карты в режиме онлайн или оффлайн с помощью <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
\nНа самом деле, OSMDashboard - это отдельное приложение, которое может получить доступ (временно) только к данным треков, которые будут показаны.
\nНа самом деле, OSMDashboard — это отдельное приложение, которое может получить доступ (временно) только к данным треков, которые будут показаны.
\nb) Через обычное картографическое приложение (например, <a href="http://osmand.net/">OsmAnd</a> и <a href="https://maps.me/">MAPS.ME</a>). Эти приложения должны поддерживать формат файлов KMZ (только местоположения и метки времени). Фактически, приложение должно поддерживать <i>ACTION_VIEW</i> с MIME <i>application/vnd.google-earth.kmz</i>. </string>
<string name="help_storage_pictures_title">Как OpenTracks хранит изображения, сделанные в качестве маркеров\?</string>
<string name="about_preference_title">О OpenTracks</string>
<string name="about_description"><i>OpenTracks</i> - это приложение для отслеживания спортивных событий, которое полностью уважает вашу конфиденциальность.</string>
<string name="about_preference_title">Об OpenTracks</string>
<string name="about_description"><i>OpenTracks</i> — это приложение для отслеживания спортивных событий, которое полностью уважает вашу конфиденциальность.</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_history">OpenTracks - это форк <a href="https://code.google.com/archive/p/mytracks/"><i>Google MyTracks</i></a> (разработка прекращена в 2016 г.).</string>
<string name="about_history">OpenTracks — это форк <a href="https://code.google.com/archive/p/mytracks/"><i>Google MyTracks</i></a> (разработка прекращена в 2016 г.).</string>
<string name="about_libraries">Библиотеки</string>
<string name="help_feature_title">OpenTracks имеет следующие возможности:</string>
<string name="help_feature_content">* создание GPS-треков
\n* использование датчика частоты пульса (только Bluetooth LE)
\n* голосовые объявления (по времени или расстоянию)
\n* создание маркеров (в т.ч. картинки)
\n* голосовые повещения (по времени или расстоянию)
\n* создание маркеров (в т.ч. с изображениями)
\n* работа полностью офлайн
\n* показ треков на карте (см. ниже)</string>
<string name="help_track_map_title">Как показать трек на карте\?</string>
<string name="help_storage_pictures_content">Они хранятся во внутренней памяти приложения (т. е. доступны только в OpenTracks).</string>
<string name="help_track_export_title">Как я могу экспортировать/поделиться записанными треками\?</string>
<string name="help_data_google_play_services_title">Требуется/используется ли OpenTracks сервисы Google Play\?</string>
<string name="help_data_google_play_services_title">Требует/использует ли OpenTracks сервисы Google Play\?</string>
<string name="help_recording_title">Как OpenTracks записывает данные\?</string>
<string name="help_track_export_content"> Трек может быть экспортирован из \"Настроек\" в виде KMZ, KML или GPX. KMZ/KML все данные; GPX только местоположения. Поделиться треком можно через значок <i>поделиться</i> предоставляется как KMZ. </string>
<string name="help_track_export_content"> Трек может быть экспортирован из \"Настроек\" в виде KMZ, KML или GPX. KMZ/KML все данные; GPX только местоположения. Поделиться треком можно через значок <i>поделиться</i> в формате KMZ. </string>
<string name="help_recording_content">OpenTracks использует датчик GPS устройства для непрерывной записи местоположения. Если устройство GPS сообщает неточные данные (например, местоположение, скорость, высоту), OpenTracks ничего не может с этим поделать.</string>
<string name="export_progress_summary_new_msg">Новые экспортируемые треки</string>
<string name="export_progress_summary_new_msg">Новых треков экспортировано</string>
<string name="help_data_storage_title">Как OpenTracks записывает данные с датчиков\?</string>
<string name="export_track_already_exists_msg">Дорожка %1$s уже существует в каталоге назначения.</string>
<string name="export_without_photos">без фотографий</string>
<string name="export_with_photos">с фотографией</string>
<string name="description_default_speed_or_pace">По типу деятельности</string>
<string name="export_all_do_it_for_all">делать это во всех конфликтах</string>
<string name="stats_loss">Потери</string>
<string name="export_progress_summary_skip_msg">Пропущенные дорожки</string>
<string name="generic_click_twice_cancel">Нажмите назад, чтобы отменить</string>
<string name="export_all_do_it_for_all">для всех конфликтов</string>
<string name="stats_loss">Спуск</string>
<string name="export_progress_summary_skip_msg">Пропущенные треки</string>
<string name="generic_click_twice_cancel">Нажмите назад еще раз, чтобы отменить</string>
<string name="generic_completed">Завершено</string>
<string name="generic_show_errors">Показать ошибки</string>
<string name="generic_separator_done_total">/</string>
<string name="generic_skip">Пропустить</string>
<string name="generic_open_track">Открытая трасса</string>
<string name="generic_open_track">Открыть трек</string>
<string name="export_progress_summary_errors_msg">Ошибки</string>
<string name="import_file_imported">Файл %1$s импортирован</string>
<string name="import_error_list_dialog_title">Файлы не импортированы</string>
@@ -405,7 +405,7 @@ limitations under the License.
<string name="open_track_as_KML">Открыть как KML</string>
<string name="help_support_title">Как поддержать OpenTracks\?</string>
<string name="help_gps_troubleshooting_title">OpenTracks не регистрирует местоположения\?</string>
<string name="activity_type_escooter">эскулап</string>
<string name="activity_type_escooter">Электросамокат</string>
<string name="permission_gps_failed">OpenTracks требует разрешения на использование GPS.</string>
<string name="settings_prevent_reimport_tracks_title">Предотвращение повторного импорта треков</string>
<string name="sensor_state_cadence_max">Макс. каденция</string>
@@ -420,33 +420,33 @@ limitations under the License.
<string name="bluetooth_disabled">Включите Bluetooth.</string>
<string name="sensor_state_power_avg">Средняя мощность</string>
<string name="track_recording_notification_accuracy">Точность расположения: %1$s</string>
<string name="stats_split_interval">Раздельный интервал</string>
<string name="stats_split_interval">Размер интервалов</string>
<string name="settings_sensor_wheel_circumference">Окружность колеса (мм)</string>
<string name="settings_night_mode_option_night">Ночная</string>
<string name="settings_night_mode_option_day">Дневная</string>
<string name="export_progress_summary_overwrite_msg">Перезапись дорожек</string>
<string name="export_track_errors">Треки не экспортируются</string>
<string name="export_progress_summary_overwrite_msg">Перезапись треков</string>
<string name="export_track_errors">Треки не экспортированы</string>
<string name="export_error_post_workout">Сбой экспорта после тренировки, пожалуйста, проверьте каталог экспорта.</string>
<string name="description_altitude_loss">Потеря высоты: %1$d м (%2$d футов)</string>
<string name="settings_default_export_uri_title">Каталог экспорта треков</string>
<string name="instant_export_enabled_summary">Экспорт дорожки в хранилище после завершения записи</string>
<string name="instant_export_enabled_summary">Экспортировать трек в хранилище после завершения записи</string>
<string name="share_image_subject">Я хотел бы поделиться с вами изображением</string>
<string name="track_detail_intervals_tab">Интервалы</string>
<string name="help_data_storage_content"> Данные датчика хранятся для каждой записи. Для датчика пульса это означает, что для каждой записи сохраняется только <i>одно</i> измерение пульса. Если вы стоите на месте или находитесь в помещении, данные датчика не будут записаны (но будут отображаться). </string>
<string name="help_gps_troubleshooting_content"> Что вы можете сделать:
<string name="help_data_storage_content"> Данные датчиков сохраняются для каждого местоположения. Для датчика пульса это означает, что для местоположения сохраняется только <i>одно</i> значение пульса. Если вы стоите на месте или находитесь в помещении, данные датчика не будут записываться (но будут отображаться). </string>
<string name="help_gps_troubleshooting_content"> Что можно сделать:
\n a) проверить, включен ли GPS,
\n б) улучшить прием GPS (например, выехать на природу),
\n б) улучшить прием GPS (например, выйти из помещения),
\n c) проверить с помощью приложения <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a>, и
\n г) отключите энергосбережение.
\n г) отключть энергосбережение.
\n
\n Если все это не поможет, пожалуйста, сообщите об ошибке. </string>
<string name="track_pace_notification">Темп: %1$s</string>
<string name="track_speed_notification">Скорость: %1$s</string>
<string name="track_distance_notification">Расстояние: %1$s</string>
<string name="interval_list_empty_message">Интервалов пока нет</string>
<string name="aggregated_stats_empty_message">Запишите свой первый трек, чтобы увидеть агрегированную статистику</string>
<string name="aggregated_stats_empty_message">Запишите свой первый трек, чтобы увидеть собранную статистику</string>
<string name="instant_export_enabled_title">Мгновенный экспорт после тренировки</string>
<string name="hold_to_stop">Удерживать для остановки</string>
<string name="hold_to_stop">Удерживайте для остановки</string>
<string name="share_image_body">Я думаю, вас может заинтересовать это изображение.</string>
<string name="help_support_content"> Вы можете:
\n a) распространять информацию об OpenTracks,
@@ -463,12 +463,11 @@ limitations under the License.
<string name="sensor_could_not_scan">Не удалось выполнить сканирование устройств Bluetooth (ошибка %1$i).</string>
<string name="import_progress_summary_errors_msg">Ошибки</string>
<string name="generic_overwrite">Перезаписать</string>
<string name="import_prevent_reimport">Вы настроили предотвращение повторного импорта трека</string>
<string name="import_prevent_reimport">Вы запретили повторный импорт трека</string>
<string name="delete_service">Услуга удаления</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="about_third_party_data">Сторонние данные</string>
<string name="menu_voice_rate">Скорость голоса</string>
<string name="track_delete_not_recording">Эта дорожка не может быть удалена, поскольку она записана в данный момент.</string>
<string name="track_delete_not_recording">Этот трек нельзя удалить, так как он записывается в данный момент.</string>
<plurals name="generic_completed_with_errors">
<item quantity="one">Завершена с %1$d ошибкой</item>
<item quantity="few">Завершено с %1$d ошибками</item>
@@ -478,7 +477,7 @@ limitations under the License.
<string name="settings_sensor_cycling_cadence">Каденция</string>
<string name="settings_sensor_cycling_distance_speed">Дистанция и Скорость</string>
<string name="settings_sensor_running_speed_and_cadence">Дистанция, Скорость и Каденция</string>
<string name="stats_sensors_cadence">Каденция</string>
<string name="stats_sensors_cadence">Каденс</string>
<string name="activity_type_swimming">Плавание</string>
<string name="activity_type_swimming_open">Плавание на открытой воде</string>
<string name="activity_type_workout">Тренировка</string>
@@ -523,7 +522,7 @@ limitations under the License.
</plurals>
<string name="settings_layout_reset">Сбросить свои раскладки</string>
<string name="settings_layout_reset_done">Настройки всех раскладок были сброшены</string>
<string name="generic_to">До</string>
<string name="generic_to">по</string>
<string name="generic_today">Сегодня</string>
<string name="generic_yesterday">Вчера</string>
<string name="settings_running_sensor">Бег</string>
@@ -537,7 +536,7 @@ limitations under the License.
<string name="settings_layout_reset_confirm_title">Сбросить свои раскладки\?</string>
<string name="settings_layout_reset_confirm_message">Настройки всех раскладок будут сброшены на значения по умолчанию. Это не удалит треки на устройстве.</string>
<string name="stats_sensors_heart_rate">Пульс</string>
<string name="stats_sensors_power">Энергия</string>
<string name="stats_sensors_power">Мощность</string>
<string name="value_float_mile_hour">%1$.1f миль/ч</string>
<string name="voice_per_kilometer">за километр</string>
<string name="voice_per_mile">за милю</string>
@@ -555,7 +554,7 @@ limitations under the License.
<string name="average_heart_rate">Средний пульс</string>
<string name="current_heart_rate">Текущий пульс</string>
<string name="generic_filter">Фильтр</string>
<string name="generic_from">От</string>
<string name="generic_from">с</string>
<string name="settings_export_section">Экспорт</string>
<string name="settings_import_section">Импорт</string>
<string name="settings_api_title">Публичный API</string>
@@ -587,4 +586,33 @@ limitations under the License.
<string name="error_crash_title">%1$s вылетел</string>
<string name="share_crash_report">Поделиться отчётом о вылете</string>
<string name="report_bug">Сообщить об ошибке</string>
<plurals name="voiceSpeedMKnots">
<item quantity="one">%1$.1f узел</item>
<item quantity="few">%1$.1f узла</item>
<item quantity="many">%1$.1f узлов</item>
<item quantity="other">%1$.1f узлов</item>
</plurals>
<string name="unit_knots">уз</string>
<plurals name="voiceDistanceNauticalMiles">
<item quantity="one">%1$.2f морская миля</item>
<item quantity="few">%1$.2f морские мили</item>
<item quantity="many">%1$.2f морских миль</item>
<item quantity="other">%1$.2f морских миль</item>
</plurals>
<string name="value_integer_knots">%1$d узлов</string>
<string name="value_float_knots">%1$.1f узла</string>
<string name="image_discard">Отменить</string>
<string name="track_discarding">Отмена записи трека</string>
<string name="settings_announcements_lap_heart_rate">Значение пульса за круг</string>
<string name="settings_announcements_lap_speed_pace">Скорость/темп за круг</string>
<string name="settings_public_api_package_title">Имя пакета для публичного API</string>
<string name="description_speed_nautical">Скорость (узлы)</string>
<string name="value_float_knots_recommended">%1$.1f узла (рекомендуется)</string>
<string name="settings_announcements_average_heart_rate">Средний пульс</string>
<string name="settings_stats_units_nautical">Морская (морские мили, фут)</string>
<string name="select_show_on_map_behavior">Выберите формат данных для показа на карте</string>
<string name="show_on_map_format_ask">Спрашивать</string>
<string name="always">Запомнить</string>
<string name="voice_per_nautical_mile">за морскую милю</string>
<string name="settings_announcements_average_speed_pace">Средняя скорость/темп</string>
</resources>
-1
View File
@@ -517,7 +517,6 @@ limitations under the License.
<string name="settings_sensors_title">Senzory Bluetooth</string>
<string name="settings_sensors_summary">Cyklistické snímače, obvod kolies, snímače behu</string>
<string name="settings_open_tracks_summary">Viac o projekte OpenTracks</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="settings_gps_summary">Interval času, interval vzdialenosti, Max. vzdialenosť, Presnosť</string>
<string name="help_support_content"/>
<string name="share_image_body">Myslím, že by vás mohol zaujímať tento obrázok.</string>
-1
View File
@@ -488,7 +488,6 @@ limitations under the License.
<string name="lap_time">Čas kroga</string>
<string name="track_detail_intervals_tab">Intervali</string>
<string name="track_delete_not_recording">Te sledi ni mogoče izbrisati, saj je trenutno posneta.</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="stats_clock">Ura</string>
<string name="settings_import_section">Uvoz</string>
<string name="share_image_subject">Rad bi z vami delil sliko</string>
-1
View File
@@ -431,7 +431,6 @@ limitations under the License.
<string name="generic_overwrite">Skriva</string>
<string name="generic_separator_done_total">/</string>
<string name="export_track_errors">Spår som inte exporteras</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="help_data_storage_content"> Sensordata lagras per inspelad plats. När det gäller en pulssensor betyder det att endast <i>en</i> pulsmätning lagras per plats. Om du står stilla eller är inomhus, kommer inga sensordata att registreras (men det kommer att visas). </string>
<string name="help_recording_title">Hur registrerar OpenTracks data\?</string>
<string name="about_description"><i>OpenTracks</i> är ett program för sportspårning som helt respekterar din integritet.</string>
-1
View File
@@ -492,7 +492,6 @@ limitations under the License.
<string name="settings_prevent_reimport_tracks_title">Parçaların yeniden içe aktarılmasını önleme</string>
<string name="stats_coordinates">Enlem/Boylam</string>
<string name="stats_sensors_power">Güç</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="aggregated_stats_filter_no_results">Belirtilen filtre için etkinlik yok</string>
<string name="value_float_kilometer_hour">%1$.1f km/s</string>
<string name="custom_layout_list_title">Dizilimler</string>
-1
View File
@@ -472,7 +472,6 @@ limitations under the License.
<string name="track_pace_notification">Темп: %1$s</string>
<string name="about_third_party_data">Дані третіх сторін</string>
<string name="delete_service">Служба видалення</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="track_delete_not_recording">Цей трек не може бути видалений в тому вигляді, в якому він записаний в даний момент.</string>
<string name="menu_voice_rate">Швидкість передачі голосу</string>
<string name="settings_sensor_cycling_cadence">Велоспорт: частота обертання педалей</string>
-1
View File
@@ -471,7 +471,6 @@ limitations under the License.
<string name="interval_list_empty_message">Không có bất kỳ khoảng thời gian sơ</string>
<string name="aggregated_stats_empty_message">Hồ sơ của đua đầu tiên, để xem tổng thống kê</string>
<string name="track_detail_intervals_tab">Khoảng thời gian</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="settings_recording_keep_screen_on_while_recording_title">Giữ trên màn hình</string>
<string name="bluetooth_disabled">Xin vui lòng cho phép Bluetooth.</string>
<string name="sensor_state_cadence_avg">Nhịp trung bình</string>
+2
View File
@@ -46,6 +46,8 @@
<string name="show_on_map_format_key" translatable="false">showOnMapFormatKey</string>
<string name="total_rows_deleted_key" translatable="false">totalRowsDeletedKey</string>
<string name="settings_default_export_directory_key" translatable="false">settingsDefaultExportDirectory</string>
<string name="post_workout_export_enabled_key" translatable="false">instantExportEnabled</string>
<bool name="post_workout_export_enabled_default" translatable="false">false</bool>
+1 -1
View File
@@ -494,7 +494,7 @@ limitations under the License.
<string name="track_delete_one_confirm_title">Delete track?</string>
<string name="track_delete_multiple_confirm_message">The selected tracks and their markers will be permanently deleted from the device.</string>
<string name="track_delete_progress_message">Deleting&#8230;</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="track_delete_number_of_tracks">%1$d tracks</string>
<string name="track_delete_not_recording">This track cannot be deleted as it is currently recorded.</string>
<string name="track_discarding">Discarding Track</string>
<!-- Track Detail -->