Public API: enable other applications to start recordings.

Fixes #671.
This commit is contained in:
Dennis Guse
2022-01-23 19:57:17 +01:00
parent 8ae4d75708
commit 838f3dc622
10 changed files with 155 additions and 20 deletions
+13 -1
View File
@@ -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.
+20 -1
View File
@@ -69,6 +69,23 @@ limitations under the License.
android:supportsRtl="true"
android:theme="@style/ThemeCustom">
<activity
android:name=".publicapi.StartRecording"
android:exported="true"
android:theme="@style/SplashTheme">
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<!-- <activity-->
<!-- android:name=".publicapi.StopRecording"-->
<!-- android:exported="true"-->
<!-- android:theme="@style/SplashTheme">-->
<!-- <intent-filter>-->
<!-- <category android:name="android.intent.category.DEFAULT" />-->
<!-- </intent-filter>-->
<!-- </activity>-->
<activity android:name=".AboutActivity" />
<activity
@@ -308,7 +325,9 @@ limitations under the License.
<activity android:name=".TrackRecordedActivity" />
<activity android:name=".TrackRecordingActivity" android:parentActivityName=".TrackListActivity">
<activity
android:name=".TrackRecordingActivity"
android:parentActivityName=".TrackListActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".TrackListActivity" />
@@ -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();
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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.");
@@ -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));
+3
View File
@@ -291,6 +291,9 @@
<string name="import_prevent_reimport_key" translatable="false">preventReimportTrackKey</string>
<bool name="import_prevent_reimport_default">true</bool>
<string name="publicapi_enabled_key" translatable="false">apiEnabledKey</string>
<bool name="publicapi_enabled_default">false</bool>
<integer name="buttonDelayMillis">1500</integer>
<!-- androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode() -->
+6
View File
@@ -445,6 +445,12 @@ limitations under the License.
<string name="settings_stats_units_metric">Metric (km, m)</string>
<string name="settings_stats_units_title">Preferred units</string>
<!-- Settings public api -->
<string name="settings_public_api_enabled_title">Public API</string>
<string name="settings_public_api_enabled_summary_on">Other installed applications can start or stop recordings.</string>
<string name="settings_public_api_enabled_summary_off">Only OpenTracks can start and stop recordings.</string>
<string name="settings_public_api_disabled_toast">OpenTracks Public API is disabled: it can be enabled in the settings.</string>
<!-- Settings import/export -->
<string name="settings_import_section">Import</string>
<string name="settings_export_section">Export</string>
+23 -16
View File
@@ -4,56 +4,63 @@
android:title="@string/menu_settings">
<Preference
android:icon="@drawable/ic_units"
android:key="@string/settings_defaults_key"
android:title="@string/settings_defaults_title"
android:summary="@string/settings_defaults_summary"
android:icon="@drawable/ic_units" />
android:title="@string/settings_defaults_title" />
<Preference
android:icon="@drawable/ic_ui"
android:key="@string/settings_ui_key"
android:title="@string/settings_ui_title"
android:summary="@string/settings_ui_summary"
android:icon="@drawable/ic_ui" />
android:title="@string/settings_ui_title" />
<Preference
android:icon="@drawable/ic_baseline_satellite_alt_24"
android:key="@string/settings_gps_key"
android:title="@string/settings_gps_title"
android:summary="@string/settings_gps_summary"
android:icon="@drawable/ic_baseline_satellite_alt_24" />
android:title="@string/settings_gps_title" />
<Preference
android:icon="@drawable/ic_baseline_sensors_24"
android:key="@string/settings_sensors_key"
android:title="@string/settings_sensors_title"
android:summary="@string/settings_sensors_summary"
android:icon="@drawable/ic_baseline_sensors_24" />
android:title="@string/settings_sensors_title" />
<Preference
android:icon="@drawable/ic_baseline_volume_up_24"
android:key="@string/settings_announcements_key"
android:title="@string/settings_announcements_title"
android:summary="@string/settings_announcements_summary"
android:icon="@drawable/ic_baseline_volume_up_24" />
android:title="@string/settings_announcements_title" />
<Preference
android:icon="@drawable/ic_baseline_import_export_24"
android:key="@string/settings_import_export_key"
android:title="@string/settings_import_export_title"
android:summary="@string/settings_import_export_summary"
android:icon="@drawable/ic_baseline_import_export_24" />
android:title="@string/settings_import_export_title" />
<de.dennisguse.opentracks.settings.ResetDialogPreference
android:dialogMessage="@string/settings_reset_confirm_message"
android:dialogTitle="@string/settings_reset_confirm_title"
android:icon="@drawable/ic_baseline_autorenew_24"
android:key="@string/settings_reset_key"
android:negativeButtonText="@android:string/cancel"
android:persistent="false"
android:positiveButtonText="@android:string/ok"
android:title="@string/settings_all_reset"
android:summary="@string/settings_reset_summary"
android:icon="@drawable/ic_baseline_autorenew_24"/>
android:title="@string/settings_all_reset" />
<SwitchPreferenceCompat
android:title="@string/settings_public_api_enabled_title"
android:summaryOn="@string/settings_public_api_enabled_summary_on"
android:summaryOff="@string/settings_public_api_enabled_summary_off"
android:defaultValue="@bool/publicapi_enabled_default"
android:key="@string/publicapi_enabled_key" />
<Preference
android:icon="@drawable/ic_baseline_info_24"
android:key="@string/settings_open_tracks_key"
android:title="@string/settings_open_tracks_title"
android:summary="@string/settings_open_tracks_summary"
android:icon="@drawable/ic_baseline_info_24" />
android:title="@string/settings_open_tracks_title" />
</PreferenceScreen>