From 207c964d6360d139cf7fa5ab5238305a0e195ebc Mon Sep 17 00:00:00 2001 From: Armin Date: Tue, 5 Mar 2024 12:59:13 +0100 Subject: [PATCH 1/5] [LEIP-206] Add draft for webdav upload --- build.gradle | 2 +- .../CyfaceDataCapturingService.java | 22 +++++++++---------- .../kotlin/de/cyface/synchronization/Auth.kt | 5 +++++ .../synchronization/CyfaceAuthenticator.kt | 10 ++++++--- .../CyfaceAuthenticatorService.kt | 12 ---------- .../synchronization/LoginActivityProvider.kt | 7 ++++++ .../cyface/synchronization/SyncPerformer.kt | 1 + 7 files changed, 32 insertions(+), 27 deletions(-) create mode 100644 synchronization/src/main/kotlin/de/cyface/synchronization/LoginActivityProvider.kt diff --git a/build.gradle b/build.gradle index 67ac50ec7..d49ac619d 100644 --- a/build.gradle +++ b/build.gradle @@ -55,7 +55,7 @@ ext { // When `android-app` is checkout out, `rootProject.uploaderVersion` in the submodules will point // to `android-app/build.gradle` instead of `submodule/build.gradle`. cyfaceSerializationVersion = "3.2.0" - cyfaceUploaderVersion = "1.2.0" + cyfaceUploaderVersion = "1.2.1-alpha1" // FIXME // Android SDK versions minSdkVersion = 21 // device support diff --git a/datacapturing/src/main/java/de/cyface/datacapturing/CyfaceDataCapturingService.java b/datacapturing/src/main/java/de/cyface/datacapturing/CyfaceDataCapturingService.java index 19116df46..712189043 100644 --- a/datacapturing/src/main/java/de/cyface/datacapturing/CyfaceDataCapturingService.java +++ b/datacapturing/src/main/java/de/cyface/datacapturing/CyfaceDataCapturingService.java @@ -19,7 +19,6 @@ package de.cyface.datacapturing; import static de.cyface.datacapturing.Constants.TAG; -import static de.cyface.synchronization.CyfaceAuthenticator.LOGIN_ACTIVITY; import java.util.ArrayList; import java.util.List; @@ -30,8 +29,6 @@ import androidx.annotation.NonNull; -import org.json.JSONObject; - import de.cyface.datacapturing.backend.DataCapturingBackgroundService; import de.cyface.datacapturing.exception.CorruptedMeasurementException; import de.cyface.datacapturing.exception.DataCapturingException; @@ -49,6 +46,7 @@ import de.cyface.persistence.strategy.DefaultLocationCleaning; import de.cyface.persistence.strategy.DistanceCalculationStrategy; import de.cyface.persistence.strategy.LocationCleaningStrategy; +import de.cyface.synchronization.LoginActivityProvider; import de.cyface.synchronization.WiFiSurveyor; import de.cyface.uploader.exception.SynchronisationException; import de.cyface.utils.Validate; @@ -85,15 +83,16 @@ public final class CyfaceDataCapturingService extends DataCapturingService { * usually uses a frequency sightly higher than this value, e.g.: 101-103/s for 100 Hz. */ private CyfaceDataCapturingService(@NonNull final Context context, - @NonNull final String authority, @NonNull final String accountType, - @NonNull final EventHandlingStrategy eventHandlingStrategy, - @NonNull final DistanceCalculationStrategy distanceCalculationStrategy, - @NonNull final LocationCleaningStrategy locationCleaningStrategy, - @NonNull final DataCapturingListener capturingListener, final int sensorFrequency) { + @NonNull final String authority, @NonNull final String accountType, + @NonNull final EventHandlingStrategy eventHandlingStrategy, + @NonNull final DistanceCalculationStrategy distanceCalculationStrategy, + @NonNull final LocationCleaningStrategy locationCleaningStrategy, + @NonNull final DataCapturingListener capturingListener, final int sensorFrequency, + @NonNull final LoginActivityProvider loginActivityProvider) { super(context, authority, accountType, eventHandlingStrategy, new DefaultPersistenceLayer<>(context, new CapturingPersistenceBehaviour()), distanceCalculationStrategy, locationCleaningStrategy, capturingListener, sensorFrequency); - if (LOGIN_ACTIVITY == null) { + if (loginActivityProvider.getLoginActivity() == null) { throw new IllegalStateException("No LOGIN_ACTIVITY was set from the SDK using app."); } } @@ -119,11 +118,12 @@ private CyfaceDataCapturingService(@NonNull final Context context, public CyfaceDataCapturingService(@NonNull final Context context, @NonNull final String authority, @NonNull final String accountType, @NonNull final EventHandlingStrategy eventHandlingStrategy, - @NonNull final DataCapturingListener capturingListener, final int sensorFrequency) + @NonNull final DataCapturingListener capturingListener, final int sensorFrequency, + @NonNull final LoginActivityProvider loginActivityProvider) throws SetupException { this(context, authority, accountType, eventHandlingStrategy, new DefaultDistanceCalculation(), new DefaultLocationCleaning(), capturingListener, - sensorFrequency); + sensorFrequency, loginActivityProvider); } /** diff --git a/synchronization/src/main/kotlin/de/cyface/synchronization/Auth.kt b/synchronization/src/main/kotlin/de/cyface/synchronization/Auth.kt index 93ff80be2..27fb74429 100644 --- a/synchronization/src/main/kotlin/de/cyface/synchronization/Auth.kt +++ b/synchronization/src/main/kotlin/de/cyface/synchronization/Auth.kt @@ -2,6 +2,11 @@ package de.cyface.synchronization interface Auth { + /** + * Makes sure that a valid access token is available before executing the provided action. + * + * @param action The method which will be executed. + */ fun performActionWithFreshTokens( action: ( accessToken: String?, diff --git a/synchronization/src/main/kotlin/de/cyface/synchronization/CyfaceAuthenticator.kt b/synchronization/src/main/kotlin/de/cyface/synchronization/CyfaceAuthenticator.kt index fd45d81c9..989a34ee6 100644 --- a/synchronization/src/main/kotlin/de/cyface/synchronization/CyfaceAuthenticator.kt +++ b/synchronization/src/main/kotlin/de/cyface/synchronization/CyfaceAuthenticator.kt @@ -39,8 +39,8 @@ import kotlinx.coroutines.runBlocking * against the Cyface server. * * **ATTENTION:** The [.getAuthToken] method is only - * called by the system if no token is cached. As our logic to invalidate token currently is in this method, we call it - * directly where we need a fresh token. + * called by the system if no token is cached. As our logic to invalidate tokens is in this method, + * we call it directly where we need a fresh token. * * @author Klemens Muthmann * @author Armin Schnabel @@ -48,7 +48,7 @@ import kotlinx.coroutines.runBlocking * @since 2.0.0 */ class CyfaceAuthenticator(private val context: Context) : - AbstractAccountAuthenticator(context) { + AbstractAccountAuthenticator(context), LoginActivityProvider { private var auth: OAuth2 = OAuth2(context, settings) @@ -171,4 +171,8 @@ class CyfaceAuthenticator(private val context: Context) : */ lateinit var settings: SynchronizationSettings } + + override fun getLoginActivity(): Class? { + return LOGIN_ACTIVITY + } } \ No newline at end of file diff --git a/synchronization/src/main/kotlin/de/cyface/synchronization/CyfaceAuthenticatorService.kt b/synchronization/src/main/kotlin/de/cyface/synchronization/CyfaceAuthenticatorService.kt index 6f2b3149f..24248d9f3 100644 --- a/synchronization/src/main/kotlin/de/cyface/synchronization/CyfaceAuthenticatorService.kt +++ b/synchronization/src/main/kotlin/de/cyface/synchronization/CyfaceAuthenticatorService.kt @@ -39,22 +39,10 @@ class CyfaceAuthenticatorService : Service() { private var authenticator: CyfaceAuthenticator? = null override fun onCreate() { - Log.d(TAG, "authenticator service on create!") - authenticator = CyfaceAuthenticator(this) } override fun onBind(intent: Intent): IBinder? { - Log.d(TAG, "authenticator service on bind") return authenticator!!.iBinder } - - companion object { - /** - * Logging TAG to identify logs associated with the [WiFiSurveyor]. - */ - @Suppress("unused") // we add and move logs often, so keep it - - val TAG = Constants.TAG + ".authSvc" - } } \ No newline at end of file diff --git a/synchronization/src/main/kotlin/de/cyface/synchronization/LoginActivityProvider.kt b/synchronization/src/main/kotlin/de/cyface/synchronization/LoginActivityProvider.kt new file mode 100644 index 000000000..0b678d438 --- /dev/null +++ b/synchronization/src/main/kotlin/de/cyface/synchronization/LoginActivityProvider.kt @@ -0,0 +1,7 @@ +package de.cyface.synchronization + +import android.app.Activity + +interface LoginActivityProvider { + fun getLoginActivity(): Class? +} \ No newline at end of file diff --git a/synchronization/src/main/kotlin/de/cyface/synchronization/SyncPerformer.kt b/synchronization/src/main/kotlin/de/cyface/synchronization/SyncPerformer.kt index 94cfc9db3..9728d98b1 100644 --- a/synchronization/src/main/kotlin/de/cyface/synchronization/SyncPerformer.kt +++ b/synchronization/src/main/kotlin/de/cyface/synchronization/SyncPerformer.kt @@ -119,6 +119,7 @@ internal class SyncPerformer(private val context: Context, private val fromBackg metaData, measurementId, file, + fileName, progressListener ) From 64c18f933122ebd657c0b7c5713a3b7d624afd95 Mon Sep 17 00:00:00 2001 From: Armin Date: Sat, 23 Mar 2024 13:53:56 +0100 Subject: [PATCH 2/5] Upgrade Uploader to 1.3.0 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d49ac619d..99006752d 100644 --- a/build.gradle +++ b/build.gradle @@ -55,7 +55,7 @@ ext { // When `android-app` is checkout out, `rootProject.uploaderVersion` in the submodules will point // to `android-app/build.gradle` instead of `submodule/build.gradle`. cyfaceSerializationVersion = "3.2.0" - cyfaceUploaderVersion = "1.2.1-alpha1" // FIXME + cyfaceUploaderVersion = "1.3.0" // Android SDK versions minSdkVersion = 21 // device support From 5d77084be7a2b3a699e9757cbbed94f4351937b5 Mon Sep 17 00:00:00 2001 From: Armin Date: Sat, 23 Mar 2024 13:55:36 +0100 Subject: [PATCH 3/5] Add licence header --- .../synchronization/LoginActivityProvider.kt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/synchronization/src/main/kotlin/de/cyface/synchronization/LoginActivityProvider.kt b/synchronization/src/main/kotlin/de/cyface/synchronization/LoginActivityProvider.kt index 0b678d438..c72d91b6a 100644 --- a/synchronization/src/main/kotlin/de/cyface/synchronization/LoginActivityProvider.kt +++ b/synchronization/src/main/kotlin/de/cyface/synchronization/LoginActivityProvider.kt @@ -1,3 +1,21 @@ +/* + * Copyright 2024 Cyface GmbH + * + * This file is part of the Cyface SDK for Android. + * + * The Cyface SDK for Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Cyface SDK for Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the Cyface SDK for Android. If not, see . + */ package de.cyface.synchronization import android.app.Activity From 6f6d2c044322122c3af4d7b198815694170dc532 Mon Sep 17 00:00:00 2001 From: Armin Date: Sat, 23 Mar 2024 14:05:11 +0100 Subject: [PATCH 4/5] Fix connectedTests --- ...CapturingServiceWithoutPermissionTest.java | 21 ++++++++++++++++--- .../datacapturing/DataCapturingServiceTest.kt | 10 ++++++++- .../de/cyface/datacapturing/PingPongTest.kt | 10 ++++++++- .../cyface/synchronization/MockedUploader.kt | 1 + .../synchronization/SyncPerformerTest.kt | 1 + 5 files changed, 38 insertions(+), 5 deletions(-) diff --git a/datacapturing/src/androidTest/java/de/cyface/datacapturing/DataCapturingServiceWithoutPermissionTest.java b/datacapturing/src/androidTest/java/de/cyface/datacapturing/DataCapturingServiceWithoutPermissionTest.java index 4311fa182..c662dab20 100644 --- a/datacapturing/src/androidTest/java/de/cyface/datacapturing/DataCapturingServiceWithoutPermissionTest.java +++ b/datacapturing/src/androidTest/java/de/cyface/datacapturing/DataCapturingServiceWithoutPermissionTest.java @@ -26,6 +26,8 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import org.json.JSONException; +import org.json.JSONObject; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -46,6 +48,7 @@ import de.cyface.datacapturing.ui.UIListener; import de.cyface.persistence.model.Modality; import de.cyface.synchronization.CyfaceAuthenticator; +import de.cyface.synchronization.settings.SynchronizationSettings; /** * Checks if missing permissions are correctly detected before starting a service. @@ -82,18 +85,30 @@ public class DataCapturingServiceWithoutPermissionTest { * Initializes the object of class under test. */ @Before - public void setUp() { + public void setUp() throws JSONException { context = InstrumentationRegistry.getInstrumentation().getTargetContext(); // The LOGIN_ACTIVITY is normally set to the LoginActivity of the SDK implementing app CyfaceAuthenticator.LOGIN_ACTIVITY = AccountAuthenticatorActivity.class; + CyfaceAuthenticator.settings = new SynchronizationSettings( + context, + "https://TEST_URL/", + new JSONObject().put("discovery_uri", "https://TEST_URL/") + ); //final String dataUploadServerAddress = "https://localhost:8080/api/v3"; final DataCapturingListener listener = new TestListener(); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { try { - oocut = new CyfaceDataCapturingService(context, TestUtils.AUTHORITY, TestUtils.ACCOUNT_TYPE, - /*dataUploadServerAddress, TestUtils.oauthConfig(),*/ new IgnoreEventsStrategy(), listener, 100); + oocut = new CyfaceDataCapturingService( + context, + TestUtils.AUTHORITY, + TestUtils.ACCOUNT_TYPE, + /*dataUploadServerAddress, TestUtils.oauthConfig(),*/ new IgnoreEventsStrategy(), + listener, + 100, + new CyfaceAuthenticator(context) + ); } catch (SetupException e) { throw new IllegalStateException(e); } diff --git a/datacapturing/src/androidTest/kotlin/de/cyface/datacapturing/DataCapturingServiceTest.kt b/datacapturing/src/androidTest/kotlin/de/cyface/datacapturing/DataCapturingServiceTest.kt index fdb6c6955..bd26eac46 100644 --- a/datacapturing/src/androidTest/kotlin/de/cyface/datacapturing/DataCapturingServiceTest.kt +++ b/datacapturing/src/androidTest/kotlin/de/cyface/datacapturing/DataCapturingServiceTest.kt @@ -46,11 +46,13 @@ import de.cyface.persistence.model.Measurement import de.cyface.persistence.model.MeasurementStatus import de.cyface.persistence.model.Modality import de.cyface.synchronization.CyfaceAuthenticator +import de.cyface.synchronization.settings.SynchronizationSettings import de.cyface.testutils.SharedTestUtils.clearPersistenceLayer import de.cyface.utils.Validate import kotlinx.coroutines.runBlocking import org.hamcrest.CoreMatchers import org.hamcrest.MatcherAssert.assertThat +import org.json.JSONObject import org.junit.After import org.junit.Before import org.junit.Ignore @@ -120,6 +122,11 @@ class DataCapturingServiceTest { // The LOGIN_ACTIVITY is normally set to the LoginActivity of the SDK implementing app CyfaceAuthenticator.LOGIN_ACTIVITY = Activity::class.java + CyfaceAuthenticator.settings = SynchronizationSettings( + context!!, + "https://TEST_URL/", + JSONObject().put("discovery_uri", "https://TEST_URL/") + ) // Add test account val requestAccount = Account(TestUtils.DEFAULT_USERNAME, TestUtils.ACCOUNT_TYPE) @@ -138,7 +145,8 @@ class DataCapturingServiceTest { //TestUtils.oauthConfig(), IgnoreEventsStrategy(), testListener!!, - 100 + 100, + CyfaceAuthenticator(context!!) ) } catch (e: SetupException) { throw IllegalStateException(e) diff --git a/datacapturing/src/androidTest/kotlin/de/cyface/datacapturing/PingPongTest.kt b/datacapturing/src/androidTest/kotlin/de/cyface/datacapturing/PingPongTest.kt index 747c59777..f0949ed82 100644 --- a/datacapturing/src/androidTest/kotlin/de/cyface/datacapturing/PingPongTest.kt +++ b/datacapturing/src/androidTest/kotlin/de/cyface/datacapturing/PingPongTest.kt @@ -37,10 +37,12 @@ import de.cyface.persistence.SetupException import de.cyface.persistence.exception.NoSuchMeasurementException import de.cyface.persistence.model.Modality import de.cyface.synchronization.CyfaceAuthenticator +import de.cyface.synchronization.settings.SynchronizationSettings import de.cyface.testutils.SharedTestUtils.clearPersistenceLayer import kotlinx.coroutines.runBlocking import org.hamcrest.CoreMatchers import org.hamcrest.MatcherAssert +import org.json.JSONObject import org.junit.After import org.junit.Before import org.junit.Rule @@ -140,6 +142,11 @@ class PingPongTest { val testListener: DataCapturingListener = TestListener() // The LOGIN_ACTIVITY is normally set to the LoginActivity of the SDK implementing app CyfaceAuthenticator.LOGIN_ACTIVITY = Activity::class.java + CyfaceAuthenticator.settings = SynchronizationSettings( + context!!, + "https://TEST_URL/", + JSONObject().put("discovery_uri", "https://TEST_URL/") + ) InstrumentationRegistry.getInstrumentation().runOnMainSync { dcs = try { CyfaceDataCapturingService( @@ -150,7 +157,8 @@ class PingPongTest { //TestUtils.oauthConfig(), IgnoreEventsStrategy(), testListener, - 100 + 100, + CyfaceAuthenticator(context!!) ) } catch (e: SetupException) { throw IllegalStateException(e) diff --git a/synchronization/src/androidTest/kotlin/de/cyface/synchronization/MockedUploader.kt b/synchronization/src/androidTest/kotlin/de/cyface/synchronization/MockedUploader.kt index 123440b06..29c0a8ceb 100644 --- a/synchronization/src/androidTest/kotlin/de/cyface/synchronization/MockedUploader.kt +++ b/synchronization/src/androidTest/kotlin/de/cyface/synchronization/MockedUploader.kt @@ -66,6 +66,7 @@ internal class MockedUploader : Uploader { metaData: RequestMetaData, measurementId: Long, file: File, + fileName: String, progressListener: UploadProgressListener ): Result { progressListener.updatedProgress(1.0f) // 100% diff --git a/synchronization/src/androidTest/kotlin/de/cyface/synchronization/SyncPerformerTest.kt b/synchronization/src/androidTest/kotlin/de/cyface/synchronization/SyncPerformerTest.kt index f36f633a0..382ae7358 100644 --- a/synchronization/src/androidTest/kotlin/de/cyface/synchronization/SyncPerformerTest.kt +++ b/synchronization/src/androidTest/kotlin/de/cyface/synchronization/SyncPerformerTest.kt @@ -311,6 +311,7 @@ class SyncPerformerTest { metaData: RequestMetaData, measurementId: Long, file: File, + fileName: String, progressListener: UploadProgressListener ): Result { throw UploadFailed(ConflictException("Test ConflictException")) From 45767453f79353b8870515fb52e036d7d2dbe0b0 Mon Sep 17 00:00:00 2001 From: Armin Date: Sat, 23 Mar 2024 15:42:32 +0100 Subject: [PATCH 5/5] Add MockSynchronizationSettings to fix duplicate DataStore file access in tests --- README.adoc | 2 +- ...CapturingServiceWithoutPermissionTest.java | 8 +- .../datacapturing/DataCapturingServiceTest.kt | 8 +- .../MockSynchronizationSettings.kt | 27 ++++ .../de/cyface/datacapturing/PingPongTest.kt | 8 +- .../settings/SynchronizationSettingsTest.kt | 6 +- .../DefaultSynchronizationSettings.kt | 122 ++++++++++++++++++ .../settings/SynchronizationSettings.kt | 106 +-------------- 8 files changed, 166 insertions(+), 121 deletions(-) create mode 100644 datacapturing/src/androidTest/kotlin/de/cyface/datacapturing/MockSynchronizationSettings.kt create mode 100644 synchronization/src/main/kotlin/de/cyface/synchronization/settings/DefaultSynchronizationSettings.kt diff --git a/README.adoc b/README.adoc index 5374bfc7a..9c4a068ae 100644 --- a/README.adoc +++ b/README.adoc @@ -433,7 +433,7 @@ class CustomApplication : Application() { // Initialize DataStore once for all settings appSettings = lazyAppSettings TrackingSettings.initialize(this) // energy_settings - CyfaceAuthenticator.settings = SynchronizationSettings( // synchronization + CyfaceAuthenticator.settings = DefaultSynchronizationSettings( // synchronization this, "https://example.com/api/v4", // Set the Data Collector URL // Movebis variant can replace oauth config with `JsonObject()` diff --git a/datacapturing/src/androidTest/java/de/cyface/datacapturing/DataCapturingServiceWithoutPermissionTest.java b/datacapturing/src/androidTest/java/de/cyface/datacapturing/DataCapturingServiceWithoutPermissionTest.java index c662dab20..e357bab3d 100644 --- a/datacapturing/src/androidTest/java/de/cyface/datacapturing/DataCapturingServiceWithoutPermissionTest.java +++ b/datacapturing/src/androidTest/java/de/cyface/datacapturing/DataCapturingServiceWithoutPermissionTest.java @@ -48,7 +48,7 @@ import de.cyface.datacapturing.ui.UIListener; import de.cyface.persistence.model.Modality; import de.cyface.synchronization.CyfaceAuthenticator; -import de.cyface.synchronization.settings.SynchronizationSettings; +import de.cyface.synchronization.settings.DefaultSynchronizationSettings; /** * Checks if missing permissions are correctly detected before starting a service. @@ -90,11 +90,7 @@ public void setUp() throws JSONException { // The LOGIN_ACTIVITY is normally set to the LoginActivity of the SDK implementing app CyfaceAuthenticator.LOGIN_ACTIVITY = AccountAuthenticatorActivity.class; - CyfaceAuthenticator.settings = new SynchronizationSettings( - context, - "https://TEST_URL/", - new JSONObject().put("discovery_uri", "https://TEST_URL/") - ); + CyfaceAuthenticator.settings = new MockSynchronizationSettings(); //final String dataUploadServerAddress = "https://localhost:8080/api/v3"; final DataCapturingListener listener = new TestListener(); diff --git a/datacapturing/src/androidTest/kotlin/de/cyface/datacapturing/DataCapturingServiceTest.kt b/datacapturing/src/androidTest/kotlin/de/cyface/datacapturing/DataCapturingServiceTest.kt index bd26eac46..96324c001 100644 --- a/datacapturing/src/androidTest/kotlin/de/cyface/datacapturing/DataCapturingServiceTest.kt +++ b/datacapturing/src/androidTest/kotlin/de/cyface/datacapturing/DataCapturingServiceTest.kt @@ -46,7 +46,7 @@ import de.cyface.persistence.model.Measurement import de.cyface.persistence.model.MeasurementStatus import de.cyface.persistence.model.Modality import de.cyface.synchronization.CyfaceAuthenticator -import de.cyface.synchronization.settings.SynchronizationSettings +import de.cyface.synchronization.settings.DefaultSynchronizationSettings import de.cyface.testutils.SharedTestUtils.clearPersistenceLayer import de.cyface.utils.Validate import kotlinx.coroutines.runBlocking @@ -122,11 +122,7 @@ class DataCapturingServiceTest { // The LOGIN_ACTIVITY is normally set to the LoginActivity of the SDK implementing app CyfaceAuthenticator.LOGIN_ACTIVITY = Activity::class.java - CyfaceAuthenticator.settings = SynchronizationSettings( - context!!, - "https://TEST_URL/", - JSONObject().put("discovery_uri", "https://TEST_URL/") - ) + CyfaceAuthenticator.settings = MockSynchronizationSettings() // Add test account val requestAccount = Account(TestUtils.DEFAULT_USERNAME, TestUtils.ACCOUNT_TYPE) diff --git a/datacapturing/src/androidTest/kotlin/de/cyface/datacapturing/MockSynchronizationSettings.kt b/datacapturing/src/androidTest/kotlin/de/cyface/datacapturing/MockSynchronizationSettings.kt new file mode 100644 index 000000000..fc59df672 --- /dev/null +++ b/datacapturing/src/androidTest/kotlin/de/cyface/datacapturing/MockSynchronizationSettings.kt @@ -0,0 +1,27 @@ +package de.cyface.datacapturing + +import de.cyface.synchronization.settings.SynchronizationSettings +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import org.json.JSONObject + +class MockSynchronizationSettings : SynchronizationSettings { + + private var _collectorUrl = MutableStateFlow("https://TEST_URL/") + private var _oAuthConfig = MutableStateFlow(JSONObject().put("discovery_uri", "https://TEST_URL/").toString()) + + override suspend fun setCollectorUrl(value: String) { + _collectorUrl.value = value + } + + override val collectorUrlFlow: Flow + get() = _collectorUrl.asStateFlow() + + override suspend fun setOAuthConfiguration(value: String) { + _oAuthConfig.value = value + } + + override val oAuthConfigurationFlow: Flow + get() = _oAuthConfig.asStateFlow() +} diff --git a/datacapturing/src/androidTest/kotlin/de/cyface/datacapturing/PingPongTest.kt b/datacapturing/src/androidTest/kotlin/de/cyface/datacapturing/PingPongTest.kt index f0949ed82..c1e7b45dd 100644 --- a/datacapturing/src/androidTest/kotlin/de/cyface/datacapturing/PingPongTest.kt +++ b/datacapturing/src/androidTest/kotlin/de/cyface/datacapturing/PingPongTest.kt @@ -37,7 +37,7 @@ import de.cyface.persistence.SetupException import de.cyface.persistence.exception.NoSuchMeasurementException import de.cyface.persistence.model.Modality import de.cyface.synchronization.CyfaceAuthenticator -import de.cyface.synchronization.settings.SynchronizationSettings +import de.cyface.synchronization.settings.DefaultSynchronizationSettings import de.cyface.testutils.SharedTestUtils.clearPersistenceLayer import kotlinx.coroutines.runBlocking import org.hamcrest.CoreMatchers @@ -142,11 +142,7 @@ class PingPongTest { val testListener: DataCapturingListener = TestListener() // The LOGIN_ACTIVITY is normally set to the LoginActivity of the SDK implementing app CyfaceAuthenticator.LOGIN_ACTIVITY = Activity::class.java - CyfaceAuthenticator.settings = SynchronizationSettings( - context!!, - "https://TEST_URL/", - JSONObject().put("discovery_uri", "https://TEST_URL/") - ) + CyfaceAuthenticator.settings = MockSynchronizationSettings() InstrumentationRegistry.getInstrumentation().runOnMainSync { dcs = try { CyfaceDataCapturingService( diff --git a/synchronization/src/androidTest/kotlin/de/cyface/synchronization/settings/SynchronizationSettingsTest.kt b/synchronization/src/androidTest/kotlin/de/cyface/synchronization/settings/SynchronizationSettingsTest.kt index 506da9c96..2688a8445 100644 --- a/synchronization/src/androidTest/kotlin/de/cyface/synchronization/settings/SynchronizationSettingsTest.kt +++ b/synchronization/src/androidTest/kotlin/de/cyface/synchronization/settings/SynchronizationSettingsTest.kt @@ -26,7 +26,7 @@ import org.junit.Before import org.junit.Test /** - * Test the inner workings of the [SynchronizationSettings]. + * Test the inner workings of the [DefaultSynchronizationSettings]. * * @author Armin Schnabel * @version 1.0.0 @@ -42,7 +42,7 @@ class SynchronizationSettingsTest { } /** - * Test that checks that the [SynchronizationSettings] constructor only accepts API URls with + * Test that checks that the [DefaultSynchronizationSettings] constructor only accepts API URls with * "https://" as protocol. * * We had twice the problem that SDK implementors used no or a false protocol. This test ensures @@ -52,7 +52,7 @@ class SynchronizationSettingsTest { @Test(expected = SetupException::class) @Throws(SetupException::class) fun testConstructor_doesNotAcceptUrlWithoutProtocol() { - SynchronizationSettings( + DefaultSynchronizationSettings( context!!, "localhost:8080/api/v3", JSONObject() diff --git a/synchronization/src/main/kotlin/de/cyface/synchronization/settings/DefaultSynchronizationSettings.kt b/synchronization/src/main/kotlin/de/cyface/synchronization/settings/DefaultSynchronizationSettings.kt new file mode 100644 index 000000000..580689dc0 --- /dev/null +++ b/synchronization/src/main/kotlin/de/cyface/synchronization/settings/DefaultSynchronizationSettings.kt @@ -0,0 +1,122 @@ +/* + * Copyright 2023 Cyface GmbH + * + * This file is part of the Cyface SDK for Android. + * + * The Cyface SDK for Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Cyface SDK for Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the Cyface SDK for Android. If not, see . + */ +package de.cyface.synchronization.settings + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.core.MultiProcessDataStoreFactory +import de.cyface.persistence.SetupException +import de.cyface.synchronization.Settings +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import org.json.JSONObject +import java.io.File + +/** + * Settings used by this library. + * + * @author Armin Schnabel + * @version 2.0.0 + * @since 7.8.1 + */ +class DefaultSynchronizationSettings(context: Context, collectorUrl: String, oAuthConfig: JSONObject) : SynchronizationSettings { + + /** + * This avoids leaking the context when this object outlives the Activity of Fragment. + */ + private val appContext = context.applicationContext + + /** + * The data store with multi-process support. + * + * The reason for multi-process support is that some settings are accessed by a background + * service which runs in another process. + * + * Attention: + * - Never mix SingleProcessDataStore with MultiProcessDataStore for the same file. + * - We use MultiProcessDataStore, i.e. the preferences can be accessed from multiple processes. + * - Only create one instance of `DataStore` per file in the same process. + * - We use ProtoBuf to ensure type safety. Rebuild after changing the .proto file. + */ + private val dataStore: DataStore = MultiProcessDataStoreFactory.create( + serializer = SettingsSerializer, + produceFile = { + // With cacheDir the settings are lost on app restart [RFR-799] + File("${appContext.filesDir.path}/synchronization.pb") + }, + migrations = listOf( + PreferencesMigrationFactory.create(appContext, collectorUrl, oAuthConfig), + StoreMigration(collectorUrl, oAuthConfig) + ) + ) + + init { + if (!collectorUrl.startsWith("https://") && !collectorUrl.startsWith("http://")) { + throw SetupException("Invalid URL protocol") + } + if (!oAuthConfig.getString("discovery_uri") + .startsWith("https://") && !oAuthConfig.getString("discovery_uri") + .startsWith("http://") + ) { + throw SetupException("Invalid URL protocol") + } + } + + /** + * Sets the URL of the server to upload data to. + * + * @param value The URL to save. + */ + override suspend fun setCollectorUrl(value: String) { + dataStore.updateData { currentSettings -> + currentSettings.toBuilder() + .setCollectorUrl(value) + .build() + } + } + + /** + * @return The URL of the server to upload data to. + */ + override val collectorUrlFlow: Flow = dataStore.data + .map { settings -> + settings.collectorUrl + } + + /** + * Sets the OAuth configuration JsonObject as String. + * + * @param value The configuration to save. + */ + override suspend fun setOAuthConfiguration(value: String) { + dataStore.updateData { currentSettings -> + currentSettings.toBuilder() + .setOAuthConfiguration(value) + .build() + } + } + + /** + * @return The OAuth configuration JsonObject as String. + */ + override val oAuthConfigurationFlow: Flow = dataStore.data + .map { settings -> + settings.oAuthConfiguration + } +} \ No newline at end of file diff --git a/synchronization/src/main/kotlin/de/cyface/synchronization/settings/SynchronizationSettings.kt b/synchronization/src/main/kotlin/de/cyface/synchronization/settings/SynchronizationSettings.kt index 2bbedc602..a36af3a48 100644 --- a/synchronization/src/main/kotlin/de/cyface/synchronization/settings/SynchronizationSettings.kt +++ b/synchronization/src/main/kotlin/de/cyface/synchronization/settings/SynchronizationSettings.kt @@ -1,122 +1,30 @@ -/* - * Copyright 2023 Cyface GmbH - * - * This file is part of the Cyface SDK for Android. - * - * The Cyface SDK for Android is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The Cyface SDK for Android is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with the Cyface SDK for Android. If not, see . - */ package de.cyface.synchronization.settings -import android.content.Context -import androidx.datastore.core.DataStore -import androidx.datastore.core.MultiProcessDataStoreFactory -import de.cyface.persistence.SetupException -import de.cyface.synchronization.Settings import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import org.json.JSONObject -import java.io.File -/** - * Settings used by this library. - * - * @author Armin Schnabel - * @version 2.0.0 - * @since 7.8.1 - */ -class SynchronizationSettings(context: Context, collectorUrl: String, oAuthConfig: JSONObject) { - - /** - * This avoids leaking the context when this object outlives the Activity of Fragment. - */ - private val appContext = context.applicationContext - - /** - * The data store with multi-process support. - * - * The reason for multi-process support is that some settings are accessed by a background - * service which runs in another process. - * - * Attention: - * - Never mix SingleProcessDataStore with MultiProcessDataStore for the same file. - * - We use MultiProcessDataStore, i.e. the preferences can be accessed from multiple processes. - * - Only create one instance of `DataStore` per file in the same process. - * - We use ProtoBuf to ensure type safety. Rebuild after changing the .proto file. - */ - private val dataStore: DataStore = MultiProcessDataStoreFactory.create( - serializer = SettingsSerializer, - produceFile = { - // With cacheDir the settings are lost on app restart [RFR-799] - File("${appContext.filesDir.path}/synchronization.pb") - }, - migrations = listOf( - PreferencesMigrationFactory.create(appContext, collectorUrl, oAuthConfig), - StoreMigration(collectorUrl, oAuthConfig) - ) - ) - - init { - if (!collectorUrl.startsWith("https://") && !collectorUrl.startsWith("http://")) { - throw SetupException("Invalid URL protocol") - } - if (!oAuthConfig.getString("discovery_uri") - .startsWith("https://") && !oAuthConfig.getString("discovery_uri") - .startsWith("http://") - ) { - throw SetupException("Invalid URL protocol") - } - } +interface SynchronizationSettings { /** * Sets the URL of the server to upload data to. * * @param value The URL to save. */ - suspend fun setCollectorUrl(value: String) { - dataStore.updateData { currentSettings -> - currentSettings.toBuilder() - .setCollectorUrl(value) - .build() - } - } + suspend fun setCollectorUrl(value: String) /** - * @return The URL of the server to upload data to. + * @return The URL of the server to upload data to as a Kotlin Flow. */ - val collectorUrlFlow: Flow = dataStore.data - .map { settings -> - settings.collectorUrl - } + val collectorUrlFlow: Flow /** * Sets the OAuth configuration JsonObject as String. * * @param value The configuration to save. */ - suspend fun setOAuthConfiguration(value: String) { - dataStore.updateData { currentSettings -> - currentSettings.toBuilder() - .setOAuthConfiguration(value) - .build() - } - } + suspend fun setOAuthConfiguration(value: String) /** - * @return The OAuth configuration JsonObject as String. + * @return The OAuth configuration JsonObject as String as a Kotlin Flow. */ - val oAuthConfigurationFlow: Flow = dataStore.data - .map { settings -> - settings.oAuthConfiguration - } + val oAuthConfigurationFlow: Flow } \ No newline at end of file