diff --git a/README.md b/README.md
index 48c3ecade..812b20302 100644
--- a/README.md
+++ b/README.md
@@ -117,7 +117,19 @@ OpenTracks can be used with [Gadgetbridge](https://www.gadgetbridge.org/):
__Only required permission:__
* _ACCESS_FINE_LOCATION_: required to use the GPS.
-An overview of Bluetooth LE sensors that are known to work with OpenTracks is in [README_TESTED_SENSORS.md](README_TESTED_SENSORS.md).
+### API
+The following functionalities by sending from an external application (e.g., [Automate](https://llamalab.com/automate/), [Tasker](https://tasker.joaoapps.com), or [Easer](https://github.com/renyuneyun/Easer)).
+The API can be invoked by sending an Intent to start an activity.
+Package (depends on the variant installed):
+* F-Droid: `de.dennisguse.opentracks`
+* GooglePlay: `de.dennisguse.opentracks.playStore`
+* Debug: `de.dennisguse.opentracks.debug`
+* Nightly: `de.dennisguse.opentracks.nightly`
+
+Activity:
+* **Start a recording:** `de.dennisguse.opentracks.publicapi.StartRecording`
+
+* For testing via adb: `adb shell am start -n "package/activity"`
## Custom Dashboards (incl. map)
As of v3.3.1, OpenTracks supports custom dashboards.
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index 470c29c6f..d6cd8d275 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -69,6 +69,23 @@ limitations under the License.
android:supportsRtl="true"
android:theme="@style/ThemeCustom">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/src/main/java/de/dennisguse/opentracks/publicapi/AbstractAPIActivity.java b/src/main/java/de/dennisguse/opentracks/publicapi/AbstractAPIActivity.java
new file mode 100644
index 000000000..153a95311
--- /dev/null
+++ b/src/main/java/de/dennisguse/opentracks/publicapi/AbstractAPIActivity.java
@@ -0,0 +1,56 @@
+package de.dennisguse.opentracks.publicapi;
+
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+
+import de.dennisguse.opentracks.R;
+import de.dennisguse.opentracks.services.TrackRecordingService;
+import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
+import de.dennisguse.opentracks.settings.PreferencesUtils;
+
+public abstract class AbstractAPIActivity extends AppCompatActivity {
+
+ private final String TAG = AbstractAPIActivity.class.getSimpleName();
+
+ private final TrackRecordingServiceConnection.Callback serviceConnectedCallback = service -> {
+ if (!isFinishing() && !isDestroyed()) {
+ execute(service);
+ }
+ if (isPostExecuteStopService()) {
+ AbstractAPIActivity.this.trackRecordingServiceConnection.unbindAndStop(AbstractAPIActivity.this);
+ } else {
+ AbstractAPIActivity.this.trackRecordingServiceConnection.unbind(AbstractAPIActivity.this);
+ }
+ finish();
+ };
+
+ private TrackRecordingServiceConnection trackRecordingServiceConnection;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (PreferencesUtils.isPublicAPIenabled()) {
+ Log.i(TAG, "Received and trying to execute requested action.");
+ trackRecordingServiceConnection = new TrackRecordingServiceConnection(serviceConnectedCallback);
+ trackRecordingServiceConnection.startAndBind(this);
+ } 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.");
+ finish();
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ trackRecordingServiceConnection = null;
+ }
+
+ protected abstract void execute(TrackRecordingService service);
+
+ protected abstract boolean isPostExecuteStopService();
+}
diff --git a/src/main/java/de/dennisguse/opentracks/publicapi/StartRecording.java b/src/main/java/de/dennisguse/opentracks/publicapi/StartRecording.java
new file mode 100644
index 000000000..c6372919e
--- /dev/null
+++ b/src/main/java/de/dennisguse/opentracks/publicapi/StartRecording.java
@@ -0,0 +1,15 @@
+package de.dennisguse.opentracks.publicapi;
+
+import de.dennisguse.opentracks.services.TrackRecordingService;
+
+public class StartRecording extends AbstractAPIActivity {
+
+ protected void execute(TrackRecordingService service) {
+ service.startNewTrack();
+ }
+
+ @Override
+ protected boolean isPostExecuteStopService() {
+ return false;
+ }
+}
diff --git a/src/main/java/de/dennisguse/opentracks/publicapi/StopRecording.java b/src/main/java/de/dennisguse/opentracks/publicapi/StopRecording.java
new file mode 100644
index 000000000..7988d8d83
--- /dev/null
+++ b/src/main/java/de/dennisguse/opentracks/publicapi/StopRecording.java
@@ -0,0 +1,15 @@
+package de.dennisguse.opentracks.publicapi;
+
+import de.dennisguse.opentracks.services.TrackRecordingService;
+
+public class StopRecording extends AbstractAPIActivity {
+
+ protected void execute(TrackRecordingService service) {
+ service.endCurrentTrack();
+ }
+
+ @Override
+ protected boolean isPostExecuteStopService() {
+ return true;
+ }
+}
diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java
index fe4626cbf..2b9742ef5 100644
--- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java
+++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java
@@ -282,7 +282,6 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
showNotification(true);
}
- @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public void endCurrentTrack() {
if (!isRecording()) {
Log.w(TAG, "Ignore endCurrentTrack. Not recording.");
diff --git a/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java b/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java
index 28e9eaf0e..61893dc92 100644
--- a/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java
+++ b/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java
@@ -28,7 +28,6 @@ import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AppCompatDelegate;
-import androidx.documentfile.provider.DocumentFile;
import androidx.preference.PreferenceManager;
import java.time.Duration;
@@ -179,6 +178,10 @@ public class PreferencesUtils {
editor.apply();
}
+ public static boolean isPublicAPIenabled() {
+ return getBoolean(R.string.publicapi_enabled_key, resources.getBoolean(R.bool.publicapi_enabled_default));
+ }
+
public static boolean isMetricUnits() {
final String STATS_UNIT = resources.getString(R.string.stats_units_default);
return STATS_UNIT.equals(getString(R.string.stats_units_key, STATS_UNIT));
diff --git a/src/main/res/values/settings.xml b/src/main/res/values/settings.xml
index 4b7eaf3d2..aa215139b 100644
--- a/src/main/res/values/settings.xml
+++ b/src/main/res/values/settings.xml
@@ -291,6 +291,9 @@
preventReimportTrackKey
true
+ apiEnabledKey
+ false
+
1500
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index defbbeb1f..10853734f 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -445,6 +445,12 @@ limitations under the License.
Metric (km, m)
Preferred units
+
+ Public API
+ Other installed applications can start or stop recordings.
+ Only OpenTracks can start and stop recordings.
+ OpenTracks Public API is disabled: it can be enabled in the settings.
+
Import
Export
diff --git a/src/main/res/xml/settings.xml b/src/main/res/xml/settings.xml
index fb17e3aec..17ee9db66 100644
--- a/src/main/res/xml/settings.xml
+++ b/src/main/res/xml/settings.xml
@@ -4,56 +4,63 @@
android:title="@string/menu_settings">
+ android:title="@string/settings_defaults_title" />
+ android:title="@string/settings_ui_title" />
+ android:title="@string/settings_gps_title" />
+ android:title="@string/settings_sensors_title" />
+ android:title="@string/settings_announcements_title" />
+ android:title="@string/settings_import_export_title" />
+ android:title="@string/settings_all_reset" />
+
+
+ android:title="@string/settings_open_tracks_title" />