From 15a3db867891d676f7771a0fae92eefe947d5985 Mon Sep 17 00:00:00 2001 From: Armin Date: Thu, 24 Aug 2023 14:24:30 +0200 Subject: [PATCH 01/12] Upgrade robolectric to 4.10.2 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d255157..6690fc6 100644 --- a/build.gradle +++ b/build.gradle @@ -65,7 +65,7 @@ ext { mockitoVersion = '5.2.0' hamcrestVersion = "1.3" rulesVersion = "1.5.0" - robolectricVersion = "4.8.2" + robolectricVersion = "4.10.2" androidxTestCoreVersion = "1.4.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" From 4d43b99c122f5c25a8a123f775222899c8ba449c Mon Sep 17 00:00:00 2001 From: Armin Date: Thu, 24 Aug 2023 14:26:35 +0200 Subject: [PATCH 02/12] Upgrade kotlin to 1.9.0 (IDE upgrade required) --- build.gradle | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index 6690fc6..b92423e 100644 --- a/build.gradle +++ b/build.gradle @@ -26,7 +26,7 @@ buildscript { ext.gradle_version = "7.4.2" - ext.kotlin_version = "1.8.20" + ext.kotlin_version = "1.9.0" repositories { google() @@ -92,8 +92,3 @@ allprojects { } apply plugin: 'android-reporting' - -// Auto-generated by Android Studio -task clean(type: Delete) { - delete rootProject.buildDir -} From 1bc6aa9dbebf6c77841a6fdbb6d17ed3ea86adbc Mon Sep 17 00:00:00 2001 From: Armin Date: Thu, 31 Aug 2023 16:04:19 +0200 Subject: [PATCH 03/12] Add Proto DataStore dependencies --- build.gradle | 9 +++- energy_settings/build.gradle | 28 +++++++++- .../energy_settings/CustomPreferences.kt | 20 ------- .../cyface/energy_settings/EnergySettings.kt | 52 ++++++++++++++++++ .../energy_settings/SettingsSerializer.kt | 51 ++++++++++++++++++ energy_settings/src/main/proto/settings.proto | 53 +++++++++++++++++++ 6 files changed, 191 insertions(+), 22 deletions(-) delete mode 100644 energy_settings/src/main/kotlin/de/cyface/energy_settings/CustomPreferences.kt create mode 100644 energy_settings/src/main/kotlin/de/cyface/energy_settings/EnergySettings.kt create mode 100644 energy_settings/src/main/kotlin/de/cyface/energy_settings/SettingsSerializer.kt create mode 100644 energy_settings/src/main/proto/settings.proto diff --git a/build.gradle b/build.gradle index b92423e..70a2f6a 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ * Top-level build file where you can add configuration options common to all sub-projects/modules. * * @author Armin Schnabel - * @version 3.2.2 + * @version 3.3.0 * @since 1.0.0 */ @@ -39,6 +39,11 @@ buildscript { } } +plugins { + // To generate Proto DataStore + id 'com.google.protobuf' version '0.8.19' apply false // Maybe keep in sync with other usages +} + ext { // This libraries version cyfaceEnergySettingsVersion = "0.0.0" // Automatically overwritten by CI @@ -56,9 +61,11 @@ ext { androidxAnnotationVersion = "1.6.0" androidxAppCompatVersion = "1.6.1" androidPreferencesVersion = '1.2.1' + datastoreVersion = "1.1.0-alpha04" // only 1.1.0 supports multi-process datastore // Other dependencies materialDialogsVersion = '3.3.0' + protobufVersion = '3.22.2' // For Proto Datastore. Maybe keep in sync with other versions. // Testing junitVersion = "1.1.5" diff --git a/energy_settings/build.gradle b/energy_settings/build.gradle index 486ecd1..a9f2080 100644 --- a/energy_settings/build.gradle +++ b/energy_settings/build.gradle @@ -20,12 +20,13 @@ * Gradle's build file for this module. * * @author Armin Schnabel - * @version 1.3.0 + * @version 1.4.0 * @since 1.0.0 */ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' +apply plugin: 'com.google.protobuf' // For Proto DataStore android { namespace "de.cyface.energy_settings" @@ -70,6 +71,11 @@ android { } dependencies { + // Proto DataStore with SingleProcess support to store settings + implementation "androidx.datastore:datastore:${datastoreVersion}" + //implementation "androidx.datastore:datastore-core-android:${datastoreVersion}" + implementation "com.google.protobuf:protobuf-javalite:${protobufVersion}" + // Other Android libraries implementation "androidx.appcompat:appcompat:$rootProject.ext.androidxAppCompatVersion" implementation "androidx.preference:preference-ktx:$rootProject.ext.androidPreferencesVersion" @@ -94,5 +100,25 @@ dependencies { testImplementation "org.robolectric:robolectric:$rootProject.ext.robolectricVersion" } +// Required for Proto DataStore +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:$protobufVersion" + } + + // Generates the java Protobuf-lite code for the Protobuf files in this project. See + // https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation + // for more information. + generateProtoTasks { + all().each { task -> + task.builtins { + java { + option 'lite' + } + } + } + } +} + // The gradle publish tasks is defined in a separate file: apply from: 'publish.gradle' diff --git a/energy_settings/src/main/kotlin/de/cyface/energy_settings/CustomPreferences.kt b/energy_settings/src/main/kotlin/de/cyface/energy_settings/CustomPreferences.kt deleted file mode 100644 index 1a65029..0000000 --- a/energy_settings/src/main/kotlin/de/cyface/energy_settings/CustomPreferences.kt +++ /dev/null @@ -1,20 +0,0 @@ -package de.cyface.energy_settings - -import android.content.Context -import androidx.core.content.edit -import de.cyface.energy_settings.Constants.PREFERENCES_MANUFACTURER_WARNING_SHOWN_KEY -import de.cyface.utils.AppPreferences - -class CustomPreferences(context: Context): AppPreferences(context) { - - fun saveWarningShown(warningShown: Boolean) { - preferences.edit { - putBoolean(PREFERENCES_MANUFACTURER_WARNING_SHOWN_KEY, warningShown) - apply() - } - } - - fun getWarningShown(): Boolean { - return preferences.getBoolean(PREFERENCES_MANUFACTURER_WARNING_SHOWN_KEY, false) - } -} \ No newline at end of file diff --git a/energy_settings/src/main/kotlin/de/cyface/energy_settings/EnergySettings.kt b/energy_settings/src/main/kotlin/de/cyface/energy_settings/EnergySettings.kt new file mode 100644 index 0000000..0351f74 --- /dev/null +++ b/energy_settings/src/main/kotlin/de/cyface/energy_settings/EnergySettings.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2023 Cyface GmbH + * + * This file is part of the Cyface Energy Settings for Android. + * + * The Cyface Energy Settings 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 Energy Settings 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 Energy Settings for Android. If not, see . + */ +package de.cyface.energy_settings + +import android.content.Context +import androidx.core.content.edit +import de.cyface.energy_settings.Constants.PREFERENCES_MANUFACTURER_WARNING_SHOWN_KEY + +/** + * Custom settings used by this library. + * + * Attention: + * - Never mix SingleProcessDataStore with MultiProcessDataStore for the same file. + * - We use SingleProcessDataStore, so don't access preferences 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. + * + * @author Armin Schnabel + * @version 2.0.0 + * @since 3.4.0 + */ +class CustomPreferences(context: Context) { + + fun saveWarningShown(warningShown: Boolean) { + //FIXME + /*preferences.edit { + putBoolean(PREFERENCES_MANUFACTURER_WARNING_SHOWN_KEY, warningShown) + apply() + }*/ + } + + fun getWarningShown(): Boolean { + return true // FIXME + //return preferences.getBoolean(PREFERENCES_MANUFACTURER_WARNING_SHOWN_KEY, false) + } +} \ No newline at end of file diff --git a/energy_settings/src/main/kotlin/de/cyface/energy_settings/SettingsSerializer.kt b/energy_settings/src/main/kotlin/de/cyface/energy_settings/SettingsSerializer.kt new file mode 100644 index 0000000..8346a07 --- /dev/null +++ b/energy_settings/src/main/kotlin/de/cyface/energy_settings/SettingsSerializer.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2023 Cyface GmbH + * + * This file is part of the Cyface App for Android. + * + * The Cyface App 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 App 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 App for Android. If not, see . + */ +package de.cyface.energy_settings + +import androidx.datastore.core.CorruptionException +import androidx.datastore.core.Serializer +import com.google.protobuf.InvalidProtocolBufferException +import java.io.InputStream +import java.io.OutputStream + +/** + * The serializer for the Proto DataStore of the preferences stored in the [Settings] file. + * + * For details: https://developer.android.com/topic/libraries/architecture/datastore#proto-datastore + * + * @author Armin Schnabel + * @since 3.7.0 + * @version 1.0.0 + */ +object SettingsSerializer : Serializer { + override val defaultValue: Settings = Settings.getDefaultInstance() + + override suspend fun readFrom(input: InputStream): Settings { + try { + return Settings.parseFrom(input) + } catch (exception: InvalidProtocolBufferException) { + throw CorruptionException("Cannot read proto.", exception) + } + } + + override suspend fun writeTo( + t: Settings, + output: OutputStream + ) = t.writeTo(output) +} \ No newline at end of file diff --git a/energy_settings/src/main/proto/settings.proto b/energy_settings/src/main/proto/settings.proto new file mode 100644 index 0000000..faeeb61 --- /dev/null +++ b/energy_settings/src/main/proto/settings.proto @@ -0,0 +1,53 @@ +/* + * Copyright 2023 Cyface GmbH + * + * This file is part of the Cyface Utils for Android. + * + * The Cyface Utils 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 Utils 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 Utils for Android. If not, see . + */ +syntax = "proto3"; + +option java_package = "de.cyface.utils"; +option java_multiple_files = true; + +/** + * The data types for the values stored in the `AppPreferences`. + * + * See https://protobuf.dev/programming-guides/proto3/ + * + * Attention: The classes are generated from the file at compile time. Don't forget to rebuild. + * + * @author: Armin Schnabel + * @since: 4.0.0 + * @version: 1.0.0 + */ +message Settings { + // Whether the map should be automatically centered while moving + bool center_map = 1; + + // Whether the app should be able to upload measurementsA. + bool upload_enabled = 2; + + // The maximum frequency with which the IMU sensors should collect data, e.g. 100 Hz. + int32 sensor_frequency = 3; + + // Whether the app should report to the error monitoring service. + bool report_errors = 4; + + // The currently selected modality, e.g. 'CAR'. + string modality = 5; + + // The API version of the terms accepted by the user, e.g. 5. + int32 accepted_terms = 6; +} \ No newline at end of file From 7f923f8b594228900ec549eff2e1c4abc2d1a313 Mon Sep 17 00:00:00 2001 From: Armin Date: Thu, 31 Aug 2023 16:05:27 +0200 Subject: [PATCH 04/12] [RFR-780] Add synchronous DataStore implementation --- .../{EnergySettings.kt => CustomSettings.kt} | 38 +++++++------ .../ProblematicManufacturerWarningDialog.kt | 14 +++-- .../energy_settings/SettingsSerializer.kt | 12 ++--- .../energy_settings/TrackingSettings.kt | 42 +++++++++++++-- .../src/main/proto/energy_settings.proto | 38 +++++++++++++ energy_settings/src/main/proto/settings.proto | 53 ------------------- 6 files changed, 114 insertions(+), 83 deletions(-) rename energy_settings/src/main/kotlin/de/cyface/energy_settings/{EnergySettings.kt => CustomSettings.kt} (60%) create mode 100644 energy_settings/src/main/proto/energy_settings.proto delete mode 100644 energy_settings/src/main/proto/settings.proto diff --git a/energy_settings/src/main/kotlin/de/cyface/energy_settings/EnergySettings.kt b/energy_settings/src/main/kotlin/de/cyface/energy_settings/CustomSettings.kt similarity index 60% rename from energy_settings/src/main/kotlin/de/cyface/energy_settings/EnergySettings.kt rename to energy_settings/src/main/kotlin/de/cyface/energy_settings/CustomSettings.kt index 0351f74..ccef4a7 100644 --- a/energy_settings/src/main/kotlin/de/cyface/energy_settings/EnergySettings.kt +++ b/energy_settings/src/main/kotlin/de/cyface/energy_settings/CustomSettings.kt @@ -18,9 +18,8 @@ */ package de.cyface.energy_settings -import android.content.Context -import androidx.core.content.edit -import de.cyface.energy_settings.Constants.PREFERENCES_MANUFACTURER_WARNING_SHOWN_KEY +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map /** * Custom settings used by this library. @@ -33,20 +32,29 @@ import de.cyface.energy_settings.Constants.PREFERENCES_MANUFACTURER_WARNING_SHOW * * @author Armin Schnabel * @version 2.0.0 - * @since 3.4.0 + * @since 3.3.4 */ -class CustomPreferences(context: Context) { +class CustomSettings { - fun saveWarningShown(warningShown: Boolean) { - //FIXME - /*preferences.edit { - putBoolean(PREFERENCES_MANUFACTURER_WARNING_SHOWN_KEY, warningShown) - apply() - }*/ + /** + * Saves whether the user marked the manufacturer-specific warning as "don't show again". + * + * @param value The boolean value to save. + */ + @Suppress("unused") // Part of the API + suspend fun setManufacturerWarningShown(value: Boolean) { + TrackingSettings.dataStore.updateData { currentSettings -> + currentSettings.toBuilder() + .setManufacturerWarningShown(value) + .build() + } } - fun getWarningShown(): Boolean { - return true // FIXME - //return preferences.getBoolean(PREFERENCES_MANUFACTURER_WARNING_SHOWN_KEY, false) - } + /** + * @return Whether user marked the manufacturer-specific warning as "don't show again". + */ + val manufacturerWarningShownFlow: Flow = TrackingSettings.dataStore.data + .map { settings -> + settings.manufacturerWarningShown + } } \ No newline at end of file diff --git a/energy_settings/src/main/kotlin/de/cyface/energy_settings/ProblematicManufacturerWarningDialog.kt b/energy_settings/src/main/kotlin/de/cyface/energy_settings/ProblematicManufacturerWarningDialog.kt index e1301a6..e9a2827 100644 --- a/energy_settings/src/main/kotlin/de/cyface/energy_settings/ProblematicManufacturerWarningDialog.kt +++ b/energy_settings/src/main/kotlin/de/cyface/energy_settings/ProblematicManufacturerWarningDialog.kt @@ -32,6 +32,8 @@ import com.afollestad.materialdialogs.MaterialDialog import de.cyface.energy_settings.Constants.TAG import de.cyface.energy_settings.GnssDisabledWarningDialog.Companion.create import de.cyface.energy_settings.ProblematicManufacturerWarningDialog.Companion.create +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import java.util.Locale /** @@ -67,7 +69,9 @@ internal class ProblematicManufacturerWarningDialog(private val recipientEmail: // Allow the user to express its preference to disable auto-popup of this dialog builder.setNegativeButton(negativeButtonRes) { _, _ -> - onNegativeButtonCall(context) + GlobalScope.launch { // FIXME + onNegativeButtonCall(context) + } } // Show Sony STAMINA specific dialog (no manufacturer specific intent name known yet) @@ -166,8 +170,8 @@ internal class ProblematicManufacturerWarningDialog(private val recipientEmail: /** * Saves the user's preference to disable auto-popup of this dialog */ - private fun onNegativeButtonCall(context: Context?) { - CustomPreferences(context!!).saveWarningShown(true) + private suspend fun onNegativeButtonCall(context: Context?) { + CustomSettings().setManufacturerWarningShown(true) } /** @@ -183,7 +187,9 @@ internal class ProblematicManufacturerWarningDialog(private val recipientEmail: // Allow the user to express its preference to disable auto-popup of this dialog dialog.negativeButton(negativeButtonRes) { - onNegativeButtonCall(activity.applicationContext) + GlobalScope.launch { // FIXME + onNegativeButtonCall(activity.applicationContext) + } } // Show Sony STAMINA specific dialog (no manufacturer specific intent name known yet) diff --git a/energy_settings/src/main/kotlin/de/cyface/energy_settings/SettingsSerializer.kt b/energy_settings/src/main/kotlin/de/cyface/energy_settings/SettingsSerializer.kt index 8346a07..5c4d87b 100644 --- a/energy_settings/src/main/kotlin/de/cyface/energy_settings/SettingsSerializer.kt +++ b/energy_settings/src/main/kotlin/de/cyface/energy_settings/SettingsSerializer.kt @@ -1,20 +1,20 @@ /* * Copyright 2023 Cyface GmbH * - * This file is part of the Cyface App for Android. + * This file is part of the Cyface Energy Settings for Android. * - * The Cyface App for Android is free software: you can redistribute it and/or modify + * The Cyface Energy Settings 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 App for Android is distributed in the hope that it will be useful, + * The Cyface Energy Settings 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 App for Android. If not, see . + * along with the Cyface Energy Settings for Android. If not, see . */ package de.cyface.energy_settings @@ -25,12 +25,12 @@ import java.io.InputStream import java.io.OutputStream /** - * The serializer for the Proto DataStore of the preferences stored in the [Settings] file. + * The serializer for the Proto DataStore of the settings stored in the [Settings] file. * * For details: https://developer.android.com/topic/libraries/architecture/datastore#proto-datastore * * @author Armin Schnabel - * @since 3.7.0 + * @since 3.4.0 * @version 1.0.0 */ object SettingsSerializer : Serializer { diff --git a/energy_settings/src/main/kotlin/de/cyface/energy_settings/TrackingSettings.kt b/energy_settings/src/main/kotlin/de/cyface/energy_settings/TrackingSettings.kt index e19b892..f29d4f7 100644 --- a/energy_settings/src/main/kotlin/de/cyface/energy_settings/TrackingSettings.kt +++ b/energy_settings/src/main/kotlin/de/cyface/energy_settings/TrackingSettings.kt @@ -28,9 +28,14 @@ import android.os.Build import android.os.PowerManager import android.util.Log import androidx.annotation.RequiresApi +import androidx.datastore.core.DataStore +import androidx.datastore.core.DataStoreFactory +import androidx.datastore.dataStoreFile import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import de.cyface.utils.Validate +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.runBlocking import java.util.Locale /** @@ -38,12 +43,33 @@ import java.util.Locale * * Offers checks and dialogs for energy settings required for background tracking. * + * Attention: You need to call [initialize] before you use this object, e.g. in Activity.onCreate. + * * @author Armin Schnabel * @version 2.0.3 * @since 1.0.0 */ object TrackingSettings { + /** + * Custom settings used by this library. + */ + private val settings: CustomSettings = CustomSettings() + + /** + * The data store with single-process support. + */ + lateinit var dataStore: DataStore + + @JvmStatic + fun initialize(context: Context) { + val dataStoreFile = context.dataStoreFile("energy_settings.pb") + dataStore = DataStoreFactory.create( + serializer = SettingsSerializer, + produceFile = { dataStoreFile } + ) + } + /** * Checks whether the energy safer mode is active *at this moment*. * @@ -311,9 +337,12 @@ object TrackingSettings { force: Boolean, recipientEmail: String ): Boolean { - - val preferences = CustomPreferences(context) - if (isProblematicManufacturer && (force || !preferences.getWarningShown())) { + // FIXME: consider async-preloading data at least, see: + // https://developer.android.com/topic/libraries/architecture/datastore#synchronous + val warningShown = runBlocking { // FIXME + settings.manufacturerWarningShownFlow.first() // FIXME + } + if (isProblematicManufacturer && (force || !warningShown)) { val fragmentManager = fragment.fragmentManager Validate.notNull(fragmentManager) val dialog = ProblematicManufacturerWarningDialog(recipientEmail) @@ -351,8 +380,11 @@ object TrackingSettings { return false } - val preferences = CustomPreferences(activity.applicationContext) - if (isProblematicManufacturer && (force || !preferences.getWarningShown())) { + val warningShown = runBlocking { // FIXME + settings.manufacturerWarningShownFlow.first() // FIXME + } + + if (isProblematicManufacturer && (force || !warningShown)) { ProblematicManufacturerWarningDialog.create(activity, recipientEmail).show() return true } diff --git a/energy_settings/src/main/proto/energy_settings.proto b/energy_settings/src/main/proto/energy_settings.proto new file mode 100644 index 0000000..c8ba478 --- /dev/null +++ b/energy_settings/src/main/proto/energy_settings.proto @@ -0,0 +1,38 @@ +/* + * Copyright 2023 Cyface GmbH + * + * This file is part of the Cyface Energy Settings for Android. + * + * The Cyface Energy Settings 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 Energy Settings 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 Energy Settings for Android. If not, see . + */ +syntax = "proto3"; + +option java_package = "de.cyface.energy_settings"; +option java_multiple_files = true; + +/** + * The data types for the values stored in the `EnergySettings`. + * + * See https://protobuf.dev/programming-guides/proto3/ + * + * Attention: The classes are generated from the file at compile time. Don't forget to rebuild. + * + * @author: Armin Schnabel + * @since: 3.4.0 + * @version: 1.0.0 + */ +message Settings { + // Whether the user marked the manufacturer-specific warning as "don't show again". + bool manufacturer_warning_shown = 1; +} \ No newline at end of file diff --git a/energy_settings/src/main/proto/settings.proto b/energy_settings/src/main/proto/settings.proto deleted file mode 100644 index faeeb61..0000000 --- a/energy_settings/src/main/proto/settings.proto +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2023 Cyface GmbH - * - * This file is part of the Cyface Utils for Android. - * - * The Cyface Utils 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 Utils 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 Utils for Android. If not, see . - */ -syntax = "proto3"; - -option java_package = "de.cyface.utils"; -option java_multiple_files = true; - -/** - * The data types for the values stored in the `AppPreferences`. - * - * See https://protobuf.dev/programming-guides/proto3/ - * - * Attention: The classes are generated from the file at compile time. Don't forget to rebuild. - * - * @author: Armin Schnabel - * @since: 4.0.0 - * @version: 1.0.0 - */ -message Settings { - // Whether the map should be automatically centered while moving - bool center_map = 1; - - // Whether the app should be able to upload measurementsA. - bool upload_enabled = 2; - - // The maximum frequency with which the IMU sensors should collect data, e.g. 100 Hz. - int32 sensor_frequency = 3; - - // Whether the app should report to the error monitoring service. - bool report_errors = 4; - - // The currently selected modality, e.g. 'CAR'. - string modality = 5; - - // The API version of the terms accepted by the user, e.g. 5. - int32 accepted_terms = 6; -} \ No newline at end of file From cf7c3f04ed1636eb326d3ed0c3442fdde38b09f8 Mon Sep 17 00:00:00 2001 From: Armin Date: Wed, 6 Sep 2023 13:34:45 +0200 Subject: [PATCH 05/12] Fix start --- energy_settings/build.gradle | 1 - .../kotlin/de/cyface/energy_settings/CustomSettings.kt | 6 ------ .../kotlin/de/cyface/energy_settings/TrackingSettings.kt | 9 ++++++++- energy_settings/src/main/proto/energy_settings.proto | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/energy_settings/build.gradle b/energy_settings/build.gradle index a9f2080..43e0b2a 100644 --- a/energy_settings/build.gradle +++ b/energy_settings/build.gradle @@ -73,7 +73,6 @@ android { dependencies { // Proto DataStore with SingleProcess support to store settings implementation "androidx.datastore:datastore:${datastoreVersion}" - //implementation "androidx.datastore:datastore-core-android:${datastoreVersion}" implementation "com.google.protobuf:protobuf-javalite:${protobufVersion}" // Other Android libraries diff --git a/energy_settings/src/main/kotlin/de/cyface/energy_settings/CustomSettings.kt b/energy_settings/src/main/kotlin/de/cyface/energy_settings/CustomSettings.kt index ccef4a7..df3a1a9 100644 --- a/energy_settings/src/main/kotlin/de/cyface/energy_settings/CustomSettings.kt +++ b/energy_settings/src/main/kotlin/de/cyface/energy_settings/CustomSettings.kt @@ -24,12 +24,6 @@ import kotlinx.coroutines.flow.map /** * Custom settings used by this library. * - * Attention: - * - Never mix SingleProcessDataStore with MultiProcessDataStore for the same file. - * - We use SingleProcessDataStore, so don't access preferences 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. - * * @author Armin Schnabel * @version 2.0.0 * @since 3.3.4 diff --git a/energy_settings/src/main/kotlin/de/cyface/energy_settings/TrackingSettings.kt b/energy_settings/src/main/kotlin/de/cyface/energy_settings/TrackingSettings.kt index f29d4f7..80b2c23 100644 --- a/energy_settings/src/main/kotlin/de/cyface/energy_settings/TrackingSettings.kt +++ b/energy_settings/src/main/kotlin/de/cyface/energy_settings/TrackingSettings.kt @@ -54,10 +54,16 @@ object TrackingSettings { /** * Custom settings used by this library. */ - private val settings: CustomSettings = CustomSettings() + private lateinit var settings: CustomSettings /** * The data store with single-process support. + * + * Attention: + * - Never mix SingleProcessDataStore with MultiProcessDataStore for the same file. + * - We use SingleProcessDataStore, so don't access preferences 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. */ lateinit var dataStore: DataStore @@ -68,6 +74,7 @@ object TrackingSettings { serializer = SettingsSerializer, produceFile = { dataStoreFile } ) + settings = CustomSettings() // Depends on dataStore to be initialized } /** diff --git a/energy_settings/src/main/proto/energy_settings.proto b/energy_settings/src/main/proto/energy_settings.proto index c8ba478..3c98b1c 100644 --- a/energy_settings/src/main/proto/energy_settings.proto +++ b/energy_settings/src/main/proto/energy_settings.proto @@ -22,7 +22,7 @@ option java_package = "de.cyface.energy_settings"; option java_multiple_files = true; /** - * The data types for the values stored in the `EnergySettings`. + * The data types for the values stored in the `CustomSettings`. * * See https://protobuf.dev/programming-guides/proto3/ * From 7d8b5796a37935ac4b86030ac6885f060f8033df Mon Sep 17 00:00:00 2001 From: Armin Date: Thu, 7 Sep 2023 16:43:13 +0200 Subject: [PATCH 06/12] [RFR-787] Add migration for sharedprefrences --- .../de/cyface/energy_settings/Constants.kt | 8 +-- .../ProblematicManufacturerWarningDialog.kt | 1 + .../energy_settings/TrackingSettings.kt | 16 ++++- .../{ => settings}/CustomSettings.kt | 3 +- .../settings/PreferencesMigration.kt | 64 +++++++++++++++++++ .../{ => settings}/SettingsSerializer.kt | 3 +- .../src/main/proto/energy_settings.proto | 6 +- 7 files changed, 90 insertions(+), 11 deletions(-) rename energy_settings/src/main/kotlin/de/cyface/energy_settings/{ => settings}/CustomSettings.kt (95%) create mode 100644 energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/PreferencesMigration.kt rename energy_settings/src/main/kotlin/de/cyface/energy_settings/{ => settings}/SettingsSerializer.kt (95%) diff --git a/energy_settings/src/main/kotlin/de/cyface/energy_settings/Constants.kt b/energy_settings/src/main/kotlin/de/cyface/energy_settings/Constants.kt index 2a11dd1..3676f4f 100644 --- a/energy_settings/src/main/kotlin/de/cyface/energy_settings/Constants.kt +++ b/energy_settings/src/main/kotlin/de/cyface/energy_settings/Constants.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019 Cyface GmbH + * Copyright 2019-2023 Cyface GmbH * * This file is part of the Cyface Energy Settings for Android. * @@ -22,13 +22,12 @@ package de.cyface.energy_settings * Holds constants required by multiple classes. * * @author Armin Schnabel - * @version 1.0.1 + * @version 1.1.0 * @since 1.0.0 */ @Suppress("SpellCheckingInspection") object Constants { const val TAG = "de.cyface.es" - private const val PACKAGE = "de.cyface.energy_settings" // Dialog codes to identify the different dialogs const val DIALOG_ENERGY_SAFER_WARNING_CODE = 2019071101 @@ -37,9 +36,6 @@ object Constants { const val DIALOG_GPS_DISABLED_WARNING_CODE = 2019071104 const val DIALOG_NO_GUIDANCE_NEEDED_DIALOG_CODE = 2019071105 - // Preference key for shared preferences - const val PREFERENCES_MANUFACTURER_WARNING_SHOWN_KEY = "$PACKAGE.manufacturer_warning_shown" - // Manufacturer names const val MANUFACTURER_HUAWEI = "huawei" const val MANUFACTURER_HONOR = "honor" // sub brand of huawei diff --git a/energy_settings/src/main/kotlin/de/cyface/energy_settings/ProblematicManufacturerWarningDialog.kt b/energy_settings/src/main/kotlin/de/cyface/energy_settings/ProblematicManufacturerWarningDialog.kt index e9a2827..24df4fc 100644 --- a/energy_settings/src/main/kotlin/de/cyface/energy_settings/ProblematicManufacturerWarningDialog.kt +++ b/energy_settings/src/main/kotlin/de/cyface/energy_settings/ProblematicManufacturerWarningDialog.kt @@ -32,6 +32,7 @@ import com.afollestad.materialdialogs.MaterialDialog import de.cyface.energy_settings.Constants.TAG import de.cyface.energy_settings.GnssDisabledWarningDialog.Companion.create import de.cyface.energy_settings.ProblematicManufacturerWarningDialog.Companion.create +import de.cyface.energy_settings.settings.CustomSettings import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import java.util.Locale diff --git a/energy_settings/src/main/kotlin/de/cyface/energy_settings/TrackingSettings.kt b/energy_settings/src/main/kotlin/de/cyface/energy_settings/TrackingSettings.kt index 80b2c23..c423f2c 100644 --- a/energy_settings/src/main/kotlin/de/cyface/energy_settings/TrackingSettings.kt +++ b/energy_settings/src/main/kotlin/de/cyface/energy_settings/TrackingSettings.kt @@ -33,6 +33,8 @@ import androidx.datastore.core.DataStoreFactory import androidx.datastore.dataStoreFile import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager +import de.cyface.energy_settings.settings.CustomSettings +import de.cyface.energy_settings.settings.SettingsSerializer import de.cyface.utils.Validate import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking @@ -56,9 +58,13 @@ object TrackingSettings { */ private lateinit var settings: CustomSettings + // FIXME: see if we can also mice the datastore to CustomSettings like everywhere else. /** * The data store with single-process support. * + * We don't use multi-process support as this should usually only run in the ui process. + * I.e. there is no need for that overhead. + * * Attention: * - Never mix SingleProcessDataStore with MultiProcessDataStore for the same file. * - We use SingleProcessDataStore, so don't access preferences from multiple processes. @@ -69,10 +75,16 @@ object TrackingSettings { @JvmStatic fun initialize(context: Context) { - val dataStoreFile = context.dataStoreFile("energy_settings.pb") + val appContext = context.applicationContext + val dataStoreFile = appContext.dataStoreFile("energy_settings.pb") dataStore = DataStoreFactory.create( serializer = SettingsSerializer, - produceFile = { dataStoreFile } + produceFile = { dataStoreFile }, + // TODO [RFR-788]: Add a test to ensure version is not set to 1 if no SharedPreferences file exist + // TODO [RFR-788]: Add a test which ensures preferences migration works and not default values are used + // TODO [RFR-788]: Add a test where the version is already 1 and SharedPreferences file is found + // TODO [RFR-788]: Add a test where the version is 1 and ensure no migration is executed / defaults are set + migrations = listOf(PreferencesMigrationFactory.create(appContext)) ) settings = CustomSettings() // Depends on dataStore to be initialized } diff --git a/energy_settings/src/main/kotlin/de/cyface/energy_settings/CustomSettings.kt b/energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/CustomSettings.kt similarity index 95% rename from energy_settings/src/main/kotlin/de/cyface/energy_settings/CustomSettings.kt rename to energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/CustomSettings.kt index df3a1a9..361d3dc 100644 --- a/energy_settings/src/main/kotlin/de/cyface/energy_settings/CustomSettings.kt +++ b/energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/CustomSettings.kt @@ -16,8 +16,9 @@ * You should have received a copy of the GNU General Public License * along with the Cyface Energy Settings for Android. If not, see . */ -package de.cyface.energy_settings +package de.cyface.energy_settings.settings +import de.cyface.energy_settings.TrackingSettings import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map diff --git a/energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/PreferencesMigration.kt b/energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/PreferencesMigration.kt new file mode 100644 index 0000000..c9c618f --- /dev/null +++ b/energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/PreferencesMigration.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2023 Cyface GmbH + * + * This file is part of the Cyface Energy Settings for Android. + * + * The Cyface Energy Settings 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 Energy Settings 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 Energy Settings for Android. If not, see . + */ +package de.cyface.energy_settings.settings + +import android.content.Context +import androidx.datastore.migrations.SharedPreferencesMigration +import androidx.datastore.migrations.SharedPreferencesView +import de.cyface.energy_settings.Settings + +/** + * Factory for the migration which imports preferences from the previously used SharedPreferences. + * + * @author Armin Schnabel + * @since 4.3.0 + */ +object PreferencesMigrationFactory { + + /** + * The filename, keys and defaults of the preferences, historically. + * + * *Don't change this, this is migration code!* + */ + private const val PREFERENCES_NAME = "AppPreferences" + private const val MANUFACTURER_WARNING_KEY = + "de.cyface.energy_settings.manufacturer_warning_shown" + + /** + * @param context The context to search and access the old SharedPreferences from. + * @return The migration code which imports preferences from the SharedPreferences if found. + */ + fun create(context: Context): SharedPreferencesMigration { + return SharedPreferencesMigration( + context, + PREFERENCES_NAME, + migrate = ::migratePreferences + ) + } + + private fun migratePreferences( + preferences: SharedPreferencesView, + settings: Settings + ): Settings { + return settings.toBuilder() + .setVersion(1) // Ensure the migrated values below are used instead of default values. + .setManufacturerWarningShown(preferences.getBoolean(MANUFACTURER_WARNING_KEY, false)) + .build() + } +} \ No newline at end of file diff --git a/energy_settings/src/main/kotlin/de/cyface/energy_settings/SettingsSerializer.kt b/energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/SettingsSerializer.kt similarity index 95% rename from energy_settings/src/main/kotlin/de/cyface/energy_settings/SettingsSerializer.kt rename to energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/SettingsSerializer.kt index 5c4d87b..9779b45 100644 --- a/energy_settings/src/main/kotlin/de/cyface/energy_settings/SettingsSerializer.kt +++ b/energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/SettingsSerializer.kt @@ -16,11 +16,12 @@ * You should have received a copy of the GNU General Public License * along with the Cyface Energy Settings for Android. If not, see . */ -package de.cyface.energy_settings +package de.cyface.energy_settings.settings import androidx.datastore.core.CorruptionException import androidx.datastore.core.Serializer import com.google.protobuf.InvalidProtocolBufferException +import de.cyface.energy_settings.Settings import java.io.InputStream import java.io.OutputStream diff --git a/energy_settings/src/main/proto/energy_settings.proto b/energy_settings/src/main/proto/energy_settings.proto index 3c98b1c..304e0f8 100644 --- a/energy_settings/src/main/proto/energy_settings.proto +++ b/energy_settings/src/main/proto/energy_settings.proto @@ -33,6 +33,10 @@ option java_multiple_files = true; * @version: 1.0.0 */ message Settings { + // Version of this datastore. It helps to support default values and to migrate data. When the + // datastore is newly created, it's 0, after default values are set it's 1, then migration happens. + uint32 version = 1; + // Whether the user marked the manufacturer-specific warning as "don't show again". - bool manufacturer_warning_shown = 1; + bool manufacturer_warning_shown = 2; } \ No newline at end of file From b283c4b73ab8ed628f26705dc386834d3d7cc178 Mon Sep 17 00:00:00 2001 From: Armin Date: Wed, 13 Sep 2023 15:31:12 +0200 Subject: [PATCH 07/12] [RFR-789] Set default values in migration code --- .../energy_settings/TrackingSettings.kt | 7 +- .../settings/PreferencesMigration.kt | 6 +- .../settings/StoreMigration.kt | 77 +++++++++++++++++++ 3 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/StoreMigration.kt diff --git a/energy_settings/src/main/kotlin/de/cyface/energy_settings/TrackingSettings.kt b/energy_settings/src/main/kotlin/de/cyface/energy_settings/TrackingSettings.kt index c423f2c..2280359 100644 --- a/energy_settings/src/main/kotlin/de/cyface/energy_settings/TrackingSettings.kt +++ b/energy_settings/src/main/kotlin/de/cyface/energy_settings/TrackingSettings.kt @@ -34,7 +34,9 @@ import androidx.datastore.dataStoreFile import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import de.cyface.energy_settings.settings.CustomSettings +import de.cyface.energy_settings.settings.PreferencesMigrationFactory import de.cyface.energy_settings.settings.SettingsSerializer +import de.cyface.energy_settings.settings.StoreMigration import de.cyface.utils.Validate import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking @@ -84,7 +86,10 @@ object TrackingSettings { // TODO [RFR-788]: Add a test which ensures preferences migration works and not default values are used // TODO [RFR-788]: Add a test where the version is already 1 and SharedPreferences file is found // TODO [RFR-788]: Add a test where the version is 1 and ensure no migration is executed / defaults are set - migrations = listOf(PreferencesMigrationFactory.create(appContext)) + migrations = listOf( + PreferencesMigrationFactory.create(appContext), + StoreMigration() + ) ) settings = CustomSettings() // Depends on dataStore to be initialized } diff --git a/energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/PreferencesMigration.kt b/energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/PreferencesMigration.kt index c9c618f..32e7897 100644 --- a/energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/PreferencesMigration.kt +++ b/energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/PreferencesMigration.kt @@ -57,7 +57,11 @@ object PreferencesMigrationFactory { settings: Settings ): Settings { return settings.toBuilder() - .setVersion(1) // Ensure the migrated values below are used instead of default values. + // Setting version to 1 as it would else default to Protobuf default of 0 which would + // trigger the StoreMigration from 0 -> 1 which ignores previous settings. + // This way the last supported version of SharedPreferences is hard-coded here and + // then the migration steps in StoreMigration starting at version 1 continues from here. + .setVersion(1) .setManufacturerWarningShown(preferences.getBoolean(MANUFACTURER_WARNING_KEY, false)) .build() } diff --git a/energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/StoreMigration.kt b/energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/StoreMigration.kt new file mode 100644 index 0000000..a8e1ea1 --- /dev/null +++ b/energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/StoreMigration.kt @@ -0,0 +1,77 @@ +/* + * Copyright 2023 Cyface GmbH + * + * This file is part of the Cyface Energy Settings for Android. + * + * The Cyface Energy Settings 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 Energy Settings 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 Energy Settings for Android. If not, see . + */ +package de.cyface.energy_settings.settings + +import android.util.Log +import androidx.datastore.core.DataMigration +import de.cyface.energy_settings.Constants.TAG +import de.cyface.energy_settings.Settings +import de.cyface.utils.settings.MigrationException + +/** + * Migration which ensures DataStore files from all versions are compatible. + * + * @author Armin Schnabel + * @version 1.0.0 + * @since 3.4.0 + */ +class StoreMigration : DataMigration { + + /** + * The current version of the datastore schema. Increase this when migration is necessary, + * e.g. when you add a new field, to ensure a correct default value is set instead of the + * Protobuf default value for that data type like "" or 0, 0.0, etc. + */ + private val currentVersion = 1 + + override suspend fun shouldMigrate(currentData: Settings): Boolean { + // When no previous datastore file exists, the version starts at 0, i.e. this ensures + // that the correct default values are set instead of the Protobuf default value for that + // data type like "" or 0, 0.0, etc. + return currentData.version < currentVersion + } + + override suspend fun migrate(currentData: Settings): Settings { + val currentVersion = currentData.version + val targetVersion = currentVersion + 1 + val logTemplate = + "Migrating ${Settings::class.java.name} from $currentVersion to $targetVersion" + Log.i(TAG, String.format(logTemplate, currentVersion, targetVersion)) + + val builder = currentData.toBuilder() + when (currentVersion) { + 0 -> { + @Suppress("UsePropertyAccessSyntax") + builder + .setVersion(targetVersion) + .setManufacturerWarningShown(false) + } + + else -> { + throw MigrationException("No migration code for version ${currentVersion}.") + } + } + return builder.build() + } + + override suspend fun cleanUp() { + // Is called when migration was successful + // Currently, there is no cleanup to do, yet + } +} From cc8ae3246f0441ae09dcce3ec9bcdf11800e2222 Mon Sep 17 00:00:00 2001 From: Armin Date: Wed, 13 Sep 2023 15:36:36 +0200 Subject: [PATCH 08/12] Rename classes --- .../ProblematicManufacturerWarningDialog.kt | 4 ++-- .../kotlin/de/cyface/energy_settings/TrackingSettings.kt | 7 ++++--- .../settings/{CustomSettings.kt => EnergySettings.kt} | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) rename energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/{CustomSettings.kt => EnergySettings.kt} (96%) diff --git a/energy_settings/src/main/kotlin/de/cyface/energy_settings/ProblematicManufacturerWarningDialog.kt b/energy_settings/src/main/kotlin/de/cyface/energy_settings/ProblematicManufacturerWarningDialog.kt index 24df4fc..d1a548d 100644 --- a/energy_settings/src/main/kotlin/de/cyface/energy_settings/ProblematicManufacturerWarningDialog.kt +++ b/energy_settings/src/main/kotlin/de/cyface/energy_settings/ProblematicManufacturerWarningDialog.kt @@ -32,7 +32,7 @@ import com.afollestad.materialdialogs.MaterialDialog import de.cyface.energy_settings.Constants.TAG import de.cyface.energy_settings.GnssDisabledWarningDialog.Companion.create import de.cyface.energy_settings.ProblematicManufacturerWarningDialog.Companion.create -import de.cyface.energy_settings.settings.CustomSettings +import de.cyface.energy_settings.settings.EnergySettings import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import java.util.Locale @@ -172,7 +172,7 @@ internal class ProblematicManufacturerWarningDialog(private val recipientEmail: * Saves the user's preference to disable auto-popup of this dialog */ private suspend fun onNegativeButtonCall(context: Context?) { - CustomSettings().setManufacturerWarningShown(true) + EnergySettings().setManufacturerWarningShown(true) } /** diff --git a/energy_settings/src/main/kotlin/de/cyface/energy_settings/TrackingSettings.kt b/energy_settings/src/main/kotlin/de/cyface/energy_settings/TrackingSettings.kt index 2280359..8d3e8a4 100644 --- a/energy_settings/src/main/kotlin/de/cyface/energy_settings/TrackingSettings.kt +++ b/energy_settings/src/main/kotlin/de/cyface/energy_settings/TrackingSettings.kt @@ -33,7 +33,7 @@ import androidx.datastore.core.DataStoreFactory import androidx.datastore.dataStoreFile import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager -import de.cyface.energy_settings.settings.CustomSettings +import de.cyface.energy_settings.settings.EnergySettings import de.cyface.energy_settings.settings.PreferencesMigrationFactory import de.cyface.energy_settings.settings.SettingsSerializer import de.cyface.energy_settings.settings.StoreMigration @@ -58,7 +58,7 @@ object TrackingSettings { /** * Custom settings used by this library. */ - private lateinit var settings: CustomSettings + private lateinit var settings: EnergySettings // FIXME: see if we can also mice the datastore to CustomSettings like everywhere else. /** @@ -91,7 +91,8 @@ object TrackingSettings { StoreMigration() ) ) - settings = CustomSettings() // Depends on dataStore to be initialized + settings = + EnergySettings() // Depends on dataStore to be initialized } /** diff --git a/energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/CustomSettings.kt b/energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/EnergySettings.kt similarity index 96% rename from energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/CustomSettings.kt rename to energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/EnergySettings.kt index 361d3dc..4fa912d 100644 --- a/energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/CustomSettings.kt +++ b/energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/EnergySettings.kt @@ -23,13 +23,13 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map /** - * Custom settings used by this library. + * Settings used by this library. * * @author Armin Schnabel * @version 2.0.0 * @since 3.3.4 */ -class CustomSettings { +class EnergySettings { /** * Saves whether the user marked the manufacturer-specific warning as "don't show again". From 97c80f2af37aa5b14bbf55a6ec560639883877de Mon Sep 17 00:00:00 2001 From: Armin Date: Thu, 14 Sep 2023 19:15:03 +0200 Subject: [PATCH 09/12] Add documentation --- .../main/kotlin/de/cyface/energy_settings/TrackingSettings.kt | 4 ---- .../de/cyface/energy_settings/settings/EnergySettings.kt | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/energy_settings/src/main/kotlin/de/cyface/energy_settings/TrackingSettings.kt b/energy_settings/src/main/kotlin/de/cyface/energy_settings/TrackingSettings.kt index 8d3e8a4..661328b 100644 --- a/energy_settings/src/main/kotlin/de/cyface/energy_settings/TrackingSettings.kt +++ b/energy_settings/src/main/kotlin/de/cyface/energy_settings/TrackingSettings.kt @@ -82,10 +82,6 @@ object TrackingSettings { dataStore = DataStoreFactory.create( serializer = SettingsSerializer, produceFile = { dataStoreFile }, - // TODO [RFR-788]: Add a test to ensure version is not set to 1 if no SharedPreferences file exist - // TODO [RFR-788]: Add a test which ensures preferences migration works and not default values are used - // TODO [RFR-788]: Add a test where the version is already 1 and SharedPreferences file is found - // TODO [RFR-788]: Add a test where the version is 1 and ensure no migration is executed / defaults are set migrations = listOf( PreferencesMigrationFactory.create(appContext), StoreMigration() diff --git a/energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/EnergySettings.kt b/energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/EnergySettings.kt index 4fa912d..37babb1 100644 --- a/energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/EnergySettings.kt +++ b/energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/EnergySettings.kt @@ -25,6 +25,10 @@ import kotlinx.coroutines.flow.map /** * Settings used by this library. * + * We currently don't use a repository to abstract the interface of the data types from the data + * source. The reason for this is the class is very simple and we don't plan multiple data sources. + * If this changes, consider using the standard Android Architecture, see `MeasurementRepository`. + * * @author Armin Schnabel * @version 2.0.0 * @since 3.3.4 From 74b8dc7b67df23574f26a20554bd780e79354583 Mon Sep 17 00:00:00 2001 From: Armin Date: Mon, 18 Sep 2023 09:41:40 +0200 Subject: [PATCH 10/12] Upgrade utils to 4.0.0 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 70a2f6a..9286e00 100644 --- a/build.gradle +++ b/build.gradle @@ -49,7 +49,7 @@ ext { cyfaceEnergySettingsVersion = "0.0.0" // Automatically overwritten by CI // Cyface dependencies - cyfaceUtilsVersion = "3.5.1" + cyfaceUtilsVersion = "4.0.0" // Android SDK versions minSdkVersion = 21 // device support From beed59ede82a0fd0f3bd6a53753a0fcd371a4a28 Mon Sep 17 00:00:00 2001 From: Armin Date: Mon, 18 Sep 2023 09:41:52 +0200 Subject: [PATCH 11/12] Upgrade compileSdkVersion to 34 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 9286e00..338cc1d 100644 --- a/build.gradle +++ b/build.gradle @@ -54,7 +54,7 @@ ext { // Android SDK versions minSdkVersion = 21 // device support targetSdkVersion = 33 // behavioral changes, follow migration guide & test the app against this - compileSdkVersion = 33 // allows newest APIs to be used and to see deprecations, use latest + compileSdkVersion = 34 // allows newest APIs to be used and to see deprecations, use latest buildToolsVersion = '34.0.0' // optional, if defined, use latest (SDK Manager > SDK Tools) // Android dependencies From 93c016fe1f130978dacb69927efbe9828524172e Mon Sep 17 00:00:00 2001 From: Armin Date: Mon, 18 Sep 2023 09:42:13 +0200 Subject: [PATCH 12/12] Cleanup --- build.gradle | 2 +- .../ProblematicManufacturerWarningDialog.kt | 38 ++++++++---- .../energy_settings/TrackingSettings.kt | 62 ++++++------------- .../settings/EnergySettings.kt | 39 ++++++++++-- 4 files changed, 83 insertions(+), 58 deletions(-) diff --git a/build.gradle b/build.gradle index 338cc1d..9fe0164 100644 --- a/build.gradle +++ b/build.gradle @@ -61,7 +61,7 @@ ext { androidxAnnotationVersion = "1.6.0" androidxAppCompatVersion = "1.6.1" androidPreferencesVersion = '1.2.1' - datastoreVersion = "1.1.0-alpha04" // only 1.1.0 supports multi-process datastore + datastoreVersion = "1.1.0-alpha05" // only 1.1.0 supports multi-process datastore // Other dependencies materialDialogsVersion = '3.3.0' diff --git a/energy_settings/src/main/kotlin/de/cyface/energy_settings/ProblematicManufacturerWarningDialog.kt b/energy_settings/src/main/kotlin/de/cyface/energy_settings/ProblematicManufacturerWarningDialog.kt index d1a548d..6ca5485 100644 --- a/energy_settings/src/main/kotlin/de/cyface/energy_settings/ProblematicManufacturerWarningDialog.kt +++ b/energy_settings/src/main/kotlin/de/cyface/energy_settings/ProblematicManufacturerWarningDialog.kt @@ -28,12 +28,12 @@ import android.content.pm.PackageManager import android.os.Build import android.os.Bundle import android.util.Log +import androidx.lifecycle.LifecycleCoroutineScope import com.afollestad.materialdialogs.MaterialDialog import de.cyface.energy_settings.Constants.TAG import de.cyface.energy_settings.GnssDisabledWarningDialog.Companion.create import de.cyface.energy_settings.ProblematicManufacturerWarningDialog.Companion.create import de.cyface.energy_settings.settings.EnergySettings -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import java.util.Locale @@ -54,12 +54,18 @@ import java.util.Locale * 2. As [MaterialDialog]. Use the static [create] method which returns the dialog. * * @author Armin Schnabel - * @version 2.1.0 + * @version 3.0.0 * @since 1.0.0 * - * @param recipientEmail The e-mail address to which the feedback email should be addressed to in the generated template. + * @property recipientEmail The e-mail address to which the feedback email should be addressed to in the generated template. + * @property scope The scope to execute async code in. + * @property settings The settings which contain the user preferences. */ -internal class ProblematicManufacturerWarningDialog(private val recipientEmail: String) : +internal class ProblematicManufacturerWarningDialog( + private val recipientEmail: String, + private val scope: LifecycleCoroutineScope, + private val settings: EnergySettings +) : EnergySettingDialog() { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { @@ -70,8 +76,8 @@ internal class ProblematicManufacturerWarningDialog(private val recipientEmail: // Allow the user to express its preference to disable auto-popup of this dialog builder.setNegativeButton(negativeButtonRes) { _, _ -> - GlobalScope.launch { // FIXME - onNegativeButtonCall(context) + scope.launch { + onNegativeButtonCall(settings) } } @@ -170,17 +176,27 @@ internal class ProblematicManufacturerWarningDialog(private val recipientEmail: /** * Saves the user's preference to disable auto-popup of this dialog + * + * @param settings The settings which contain the user preferences. */ - private suspend fun onNegativeButtonCall(context: Context?) { - EnergySettings().setManufacturerWarningShown(true) + private suspend fun onNegativeButtonCall(settings: EnergySettings) { + settings.setManufacturerWarningShown(true) } /** * Alternative, `FragmentManager`-less implementation. * * @param activity Required to show the dialog + * @param recipientEmail The e-mail address to which the feedback email should be addressed to in the generated template. + * @param settings The settings which contain the user preferences. + * @param scope The scope to execute async code in. */ - fun create(activity: Activity, recipientEmail: String): MaterialDialog { + fun create( + activity: Activity, + recipientEmail: String, + settings: EnergySettings, + scope: LifecycleCoroutineScope + ): MaterialDialog { // Generate dialog val dialog = MaterialDialog(activity, DIALOG_BEHAVIOUR) @@ -188,8 +204,8 @@ internal class ProblematicManufacturerWarningDialog(private val recipientEmail: // Allow the user to express its preference to disable auto-popup of this dialog dialog.negativeButton(negativeButtonRes) { - GlobalScope.launch { // FIXME - onNegativeButtonCall(activity.applicationContext) + scope.launch { + onNegativeButtonCall(settings) } } diff --git a/energy_settings/src/main/kotlin/de/cyface/energy_settings/TrackingSettings.kt b/energy_settings/src/main/kotlin/de/cyface/energy_settings/TrackingSettings.kt index 661328b..19eb481 100644 --- a/energy_settings/src/main/kotlin/de/cyface/energy_settings/TrackingSettings.kt +++ b/energy_settings/src/main/kotlin/de/cyface/energy_settings/TrackingSettings.kt @@ -28,15 +28,12 @@ import android.os.Build import android.os.PowerManager import android.util.Log import androidx.annotation.RequiresApi -import androidx.datastore.core.DataStore -import androidx.datastore.core.DataStoreFactory -import androidx.datastore.dataStoreFile import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager +import androidx.lifecycle.LifecycleCoroutineScope +import androidx.lifecycle.lifecycleScope +import de.cyface.energy_settings.TrackingSettings.initialize import de.cyface.energy_settings.settings.EnergySettings -import de.cyface.energy_settings.settings.PreferencesMigrationFactory -import de.cyface.energy_settings.settings.SettingsSerializer -import de.cyface.energy_settings.settings.StoreMigration import de.cyface.utils.Validate import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking @@ -50,7 +47,7 @@ import java.util.Locale * Attention: You need to call [initialize] before you use this object, e.g. in Activity.onCreate. * * @author Armin Schnabel - * @version 2.0.3 + * @version 2.1.0 * @since 1.0.0 */ object TrackingSettings { @@ -60,35 +57,9 @@ object TrackingSettings { */ private lateinit var settings: EnergySettings - // FIXME: see if we can also mice the datastore to CustomSettings like everywhere else. - /** - * The data store with single-process support. - * - * We don't use multi-process support as this should usually only run in the ui process. - * I.e. there is no need for that overhead. - * - * Attention: - * - Never mix SingleProcessDataStore with MultiProcessDataStore for the same file. - * - We use SingleProcessDataStore, so don't access preferences 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. - */ - lateinit var dataStore: DataStore - @JvmStatic fun initialize(context: Context) { - val appContext = context.applicationContext - val dataStoreFile = appContext.dataStoreFile("energy_settings.pb") - dataStore = DataStoreFactory.create( - serializer = SettingsSerializer, - produceFile = { dataStoreFile }, - migrations = listOf( - PreferencesMigrationFactory.create(appContext), - StoreMigration() - ) - ) - settings = - EnergySettings() // Depends on dataStore to be initialized + settings = EnergySettings(context) } /** @@ -358,15 +329,19 @@ object TrackingSettings { force: Boolean, recipientEmail: String ): Boolean { - // FIXME: consider async-preloading data at least, see: + // TODO: consider async-preloading data at least, see: // https://developer.android.com/topic/libraries/architecture/datastore#synchronous - val warningShown = runBlocking { // FIXME - settings.manufacturerWarningShownFlow.first() // FIXME + val warningShown = runBlocking { + settings.manufacturerWarningShownFlow.first() } if (isProblematicManufacturer && (force || !warningShown)) { val fragmentManager = fragment.fragmentManager Validate.notNull(fragmentManager) - val dialog = ProblematicManufacturerWarningDialog(recipientEmail) + val dialog = ProblematicManufacturerWarningDialog( + recipientEmail, + fragment.lifecycleScope, + settings + ) dialog.setTargetFragment( fragment, Constants.DIALOG_PROBLEMATIC_MANUFACTURER_WARNING_CODE @@ -386,6 +361,7 @@ object TrackingSettings { * @param recipientEmail The e-mail address to which the feedback email should be addressed to in the generated * template. * @param force `True` if the dialog should be shown no matter of the preferences state + * @param scope The scope to execute async code in. * @return `True` if the dialog is shown */ @JvmStatic @@ -393,7 +369,8 @@ object TrackingSettings { fun showProblematicManufacturerDialog( activity: Activity?, force: Boolean, - recipientEmail: String + recipientEmail: String, + scope: LifecycleCoroutineScope ): Boolean { if (activity == null || activity.isFinishing) { @@ -401,12 +378,13 @@ object TrackingSettings { return false } - val warningShown = runBlocking { // FIXME - settings.manufacturerWarningShownFlow.first() // FIXME + val warningShown = runBlocking { + settings.manufacturerWarningShownFlow.first() } if (isProblematicManufacturer && (force || !warningShown)) { - ProblematicManufacturerWarningDialog.create(activity, recipientEmail).show() + ProblematicManufacturerWarningDialog.create(activity, recipientEmail, settings, scope) + .show() return true } return false diff --git a/energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/EnergySettings.kt b/energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/EnergySettings.kt index 37babb1..10bd718 100644 --- a/energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/EnergySettings.kt +++ b/energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/EnergySettings.kt @@ -18,7 +18,11 @@ */ package de.cyface.energy_settings.settings -import de.cyface.energy_settings.TrackingSettings +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.core.DataStoreFactory +import androidx.datastore.dataStoreFile +import de.cyface.energy_settings.Settings import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -32,8 +36,35 @@ import kotlinx.coroutines.flow.map * @author Armin Schnabel * @version 2.0.0 * @since 3.3.4 + * @param context The context to access the settings from. */ -class EnergySettings { +class EnergySettings(context: Context) { + + /** + * This avoids leaking the context when this object outlives the Activity of Fragment. + */ + private val appContext = context.applicationContext + + /** + * The data store with single-process support. + * + * We don't use multi-process support as this should usually only run in the ui process. + * I.e. there is no need for that overhead. + * + * Attention: + * - Never mix SingleProcessDataStore with MultiProcessDataStore for the same file. + * - We use SingleProcessDataStore, so don't access preferences 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 var dataStore: DataStore = DataStoreFactory.create( + serializer = SettingsSerializer, + produceFile = { appContext.dataStoreFile("energy_settings.pb") }, + migrations = listOf( + PreferencesMigrationFactory.create(appContext), + StoreMigration() + ) + ) /** * Saves whether the user marked the manufacturer-specific warning as "don't show again". @@ -42,7 +73,7 @@ class EnergySettings { */ @Suppress("unused") // Part of the API suspend fun setManufacturerWarningShown(value: Boolean) { - TrackingSettings.dataStore.updateData { currentSettings -> + dataStore.updateData { currentSettings -> currentSettings.toBuilder() .setManufacturerWarningShown(value) .build() @@ -52,7 +83,7 @@ class EnergySettings { /** * @return Whether user marked the manufacturer-specific warning as "don't show again". */ - val manufacturerWarningShownFlow: Flow = TrackingSettings.dataStore.data + val manufacturerWarningShownFlow: Flow = dataStore.data .map { settings -> settings.manufacturerWarningShown }