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" />