Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Task/rfr 769 migrate to datastore #59

Merged
merged 12 commits into from
Sep 18, 2023
22 changes: 12 additions & 10 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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"

Expand All @@ -92,8 +99,3 @@ allprojects {
}

apply plugin: 'android-reporting'

// Auto-generated by Android Studio
task clean(type: Delete) {
delete rootProject.buildDir
}
27 changes: 26 additions & 1 deletion energy_settings/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand All @@ -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'
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2019 Cyface GmbH
* Copyright 2019-2023 Cyface GmbH
*
* This file is part of the Cyface Energy Settings for Android.
*
Expand All @@ -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
Expand All @@ -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
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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

/**
Expand All @@ -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 {
Expand All @@ -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)
Expand Down Expand Up @@ -165,25 +176,37 @@ 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)
dialog.title(titleRes, null)

// 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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,38 @@ 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

/**
* Holds the API for this energy setting library.
*
* 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*.
*
Expand Down Expand Up @@ -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
Expand All @@ -336,24 +361,30 @@ 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
@Suppress("MemberVisibilityCanBePrivate") // Used by implementing app
fun showProblematicManufacturerDialog(
activity: Activity?,
force: Boolean,
recipientEmail: String
recipientEmail: String,
scope: LifecycleCoroutineScope
): Boolean {

if (activity == null || activity.isFinishing) {
Log.w(Constants.TAG, "showProblematicManufacturerDialog: aborted, activity is null")
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
Expand Down
Loading