diff --git a/build.gradle b/build.gradle
index d255157..9fe0164 100644
--- a/build.gradle
+++ b/build.gradle
@@ -20,13 +20,13 @@
* 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
*/
buildscript {
ext.gradle_version = "7.4.2"
- ext.kotlin_version = "1.8.20"
+ ext.kotlin_version = "1.9.0"
repositories {
google()
@@ -39,33 +39,40 @@ 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
// Cyface dependencies
- cyfaceUtilsVersion = "3.5.1"
+ cyfaceUtilsVersion = "4.0.0"
// 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
androidxAnnotationVersion = "1.6.0"
androidxAppCompatVersion = "1.6.1"
androidPreferencesVersion = '1.2.1'
+ datastoreVersion = "1.1.0-alpha05" // 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"
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"
@@ -92,8 +99,3 @@ allprojects {
}
apply plugin: 'android-reporting'
-
-// Auto-generated by Android Studio
-task clean(type: Delete) {
- delete rootProject.buildDir
-}
diff --git a/energy_settings/build.gradle b/energy_settings/build.gradle
index 486ecd1..43e0b2a 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,10 @@ android {
}
dependencies {
+ // Proto DataStore with SingleProcess support to store settings
+ implementation "androidx.datastore:datastore:${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 +99,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/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/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/ProblematicManufacturerWarningDialog.kt b/energy_settings/src/main/kotlin/de/cyface/energy_settings/ProblematicManufacturerWarningDialog.kt
index e1301a6..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,10 +28,13 @@ 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.launch
import java.util.Locale
/**
@@ -51,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 {
@@ -67,7 +76,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)
+ scope.launch {
+ onNegativeButtonCall(settings)
+ }
}
// Show Sony STAMINA specific dialog (no manufacturer specific intent name known yet)
@@ -165,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 fun onNegativeButtonCall(context: Context?) {
- CustomPreferences(context!!).saveWarningShown(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)
@@ -183,7 +204,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)
+ scope.launch {
+ onNegativeButtonCall(settings)
+ }
}
// Show Sony STAMINA specific dialog (no manufacturer specific intent name known yet)
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..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
@@ -30,7 +30,13 @@ import android.util.Log
import androidx.annotation.RequiresApi
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.utils.Validate
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.runBlocking
import java.util.Locale
/**
@@ -38,12 +44,24 @@ 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
+ * @version 2.1.0
* @since 1.0.0
*/
object TrackingSettings {
+ /**
+ * Custom settings used by this library.
+ */
+ private lateinit var settings: EnergySettings
+
+ @JvmStatic
+ fun initialize(context: Context) {
+ settings = EnergySettings(context)
+ }
+
/**
* Checks whether the energy safer mode is active *at this moment*.
*
@@ -311,12 +329,19 @@ object TrackingSettings {
force: Boolean,
recipientEmail: String
): Boolean {
-
- val preferences = CustomPreferences(context)
- if (isProblematicManufacturer && (force || !preferences.getWarningShown())) {
+ // TODO: consider async-preloading data at least, see:
+ // https://developer.android.com/topic/libraries/architecture/datastore#synchronous
+ 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
@@ -336,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
@@ -343,7 +369,8 @@ object TrackingSettings {
fun showProblematicManufacturerDialog(
activity: Activity?,
force: Boolean,
- recipientEmail: String
+ recipientEmail: String,
+ scope: LifecycleCoroutineScope
): Boolean {
if (activity == null || activity.isFinishing) {
@@ -351,9 +378,13 @@ object TrackingSettings {
return false
}
- val preferences = CustomPreferences(activity.applicationContext)
- if (isProblematicManufacturer && (force || !preferences.getWarningShown())) {
- ProblematicManufacturerWarningDialog.create(activity, recipientEmail).show()
+ val warningShown = runBlocking {
+ settings.manufacturerWarningShownFlow.first()
+ }
+
+ if (isProblematicManufacturer && (force || !warningShown)) {
+ 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
new file mode 100644
index 0000000..10bd718
--- /dev/null
+++ b/energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/EnergySettings.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.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
+
+/**
+ * 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
+ * @param context The context to access the settings from.
+ */
+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".
+ *
+ * @param value The boolean value to save.
+ */
+ @Suppress("unused") // Part of the API
+ suspend fun setManufacturerWarningShown(value: Boolean) {
+ dataStore.updateData { currentSettings ->
+ currentSettings.toBuilder()
+ .setManufacturerWarningShown(value)
+ .build()
+ }
+ }
+
+ /**
+ * @return Whether user marked the manufacturer-specific warning as "don't show again".
+ */
+ val manufacturerWarningShownFlow: Flow = dataStore.data
+ .map { settings ->
+ settings.manufacturerWarningShown
+ }
+}
\ No newline at end of file
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..32e7897
--- /dev/null
+++ b/energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/PreferencesMigration.kt
@@ -0,0 +1,68 @@
+/*
+ * 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()
+ // 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()
+ }
+}
\ No newline at end of file
diff --git a/energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/SettingsSerializer.kt b/energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/SettingsSerializer.kt
new file mode 100644
index 0000000..9779b45
--- /dev/null
+++ b/energy_settings/src/main/kotlin/de/cyface/energy_settings/settings/SettingsSerializer.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.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
+
+/**
+ * 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.4.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/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
+ }
+}
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..304e0f8
--- /dev/null
+++ b/energy_settings/src/main/proto/energy_settings.proto
@@ -0,0 +1,42 @@
+/*
+ * 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 `CustomSettings`.
+ *
+ * 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 {
+ // 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 = 2;
+}
\ No newline at end of file