Commit bf115e14 authored by stopcovid@lunabee.com's avatar stopcovid@lunabee.com
Browse files

Update to 2.6.1

- Add possibility to opt-out of the analytics
parent f5d8a8da
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Authors
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Created by Lunabee Studio / Date - 2021/27/04 - for the TOUS-ANTI-COVID project
*/
package com.lunabeestudio.analytics.extension
import android.content.SharedPreferences
import androidx.core.content.edit
private const val SHARED_PREFS_INSTALLATION_UUID: String = "Shared.Prefs.Installation.UUID"
private const val SHARED_PREFS_PROXIMITY_START_TIME: String = "Shared.Prefs.Proximity.Start.Time"
private const val SHARED_PREFS_PROXIMITY_ACTIVE_DURATION: String = "Shared.Prefs.Proximity.Active.Duration"
private const val SHARED_PREFS_STATUS_SUCCESS_COUNT: String = "Shared.Prefs.Status.Success.Count"
private const val SHARED_PREFS_IS_OPT_IN: String = "Shared.Prefs.Is.Opt.In"
var SharedPreferences.installationUUID: String?
get() = getString(SHARED_PREFS_INSTALLATION_UUID, null)
set(value) = edit {
if (value.isNullOrBlank()) {
remove(SHARED_PREFS_INSTALLATION_UUID)
} else {
putString(SHARED_PREFS_INSTALLATION_UUID, value)
}
}
var SharedPreferences.proximityStartTime: Long?
get() = if (contains(SHARED_PREFS_PROXIMITY_START_TIME)) {
getLong(SHARED_PREFS_PROXIMITY_START_TIME, System.currentTimeMillis())
} else {
null
}
set(value) = edit {
if (value == null) {
remove(SHARED_PREFS_PROXIMITY_START_TIME)
} else {
putLong(SHARED_PREFS_PROXIMITY_START_TIME, value)
}
}
var SharedPreferences.proximityActiveDuration: Long
get() = getLong(SHARED_PREFS_PROXIMITY_ACTIVE_DURATION, 0L)
set(value) = edit {
putLong(SHARED_PREFS_PROXIMITY_ACTIVE_DURATION, value)
}
var SharedPreferences.statusSuccessCount: Int
get() = getInt(SHARED_PREFS_STATUS_SUCCESS_COUNT, 0)
set(value) = edit {
putInt(SHARED_PREFS_STATUS_SUCCESS_COUNT, value)
}
var SharedPreferences.isOptIn: Boolean
get() = getBoolean(SHARED_PREFS_IS_OPT_IN, true)
set(value) = edit {
putBoolean(SHARED_PREFS_IS_OPT_IN, value)
}
\ No newline at end of file
......@@ -13,9 +13,13 @@ package com.lunabeestudio.analytics.manager
import android.content.Context
import android.content.SharedPreferences
import android.os.Build
import androidx.core.content.edit
import androidx.core.util.AtomicFile
import androidx.lifecycle.LifecycleObserver
import com.lunabeestudio.analytics.extension.installationUUID
import com.lunabeestudio.analytics.extension.isOptIn
import com.lunabeestudio.analytics.extension.proximityActiveDuration
import com.lunabeestudio.analytics.extension.proximityStartTime
import com.lunabeestudio.analytics.extension.statusSuccessCount
import com.lunabeestudio.analytics.extension.toAPI
import com.lunabeestudio.analytics.extension.toDomain
import com.lunabeestudio.analytics.extension.toProto
......@@ -50,19 +54,20 @@ object AnalyticsManager : LifecycleObserver {
private const val FILE_NAME_APP_ERRORS: String = "app_errors"
private const val FILE_NAME_HEALTH_EVENTS: String = "heath_events"
private const val SHARED_PREFS_NAME: String = "TacAnalytics"
private const val SHARED_PREFS_INSTALLATION_UUID: String = "Shared.Prefs.Installation.UUID"
private const val SHARED_PREFS_PROXIMITY_START_TIME: String = "Shared.Prefs.Proximity.Start.Time"
private const val SHARED_PREFS_PROXIMITY_ACTIVE_DURATION: String = "Shared.Prefs.Proximity.Active.Duration"
private const val SHARED_PREFS_STATUS_SUCCESS_COUNT: String = "Shared.Prefs.Status.Success.Count"
private val dateFormat: DateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.FRANCE)
private lateinit var sharedPreferences: SharedPreferences
fun isOptIn(context: Context): Boolean = getSharedPrefs(context).isOptIn
fun setIsOptIn(context: Context, isOptIn: Boolean) {
getSharedPrefs(context).isOptIn = isOptIn
}
fun init(context: Context) {
if (!getSharedPrefs(context).contains(SHARED_PREFS_INSTALLATION_UUID)) {
getSharedPrefs(context).edit {
putString(SHARED_PREFS_INSTALLATION_UUID, UUID.randomUUID().toString())
getSharedPrefs(context).apply {
if (installationUUID == null) {
installationUUID = UUID.randomUUID().toString()
}
}
}
......@@ -75,47 +80,40 @@ object AnalyticsManager : LifecycleObserver {
}
fun register(context: Context) {
getSharedPrefs(context).edit {
putString(SHARED_PREFS_INSTALLATION_UUID, UUID.randomUUID().toString())
}
getSharedPrefs(context).installationUUID = UUID.randomUUID().toString()
}
fun unregister(context: Context) {
getSharedPrefs(context).edit {
remove(SHARED_PREFS_INSTALLATION_UUID)
remove(SHARED_PREFS_PROXIMITY_START_TIME)
remove(SHARED_PREFS_PROXIMITY_ACTIVE_DURATION)
remove(SHARED_PREFS_STATUS_SUCCESS_COUNT)
getSharedPrefs(context).apply {
installationUUID = null
proximityStartTime = null
proximityActiveDuration = 0L
statusSuccessCount = 0
}
reset(context)
}
fun proximityDidStart(context: Context) {
getSharedPrefs(context).edit {
putLong(SHARED_PREFS_PROXIMITY_START_TIME, System.currentTimeMillis())
}
getSharedPrefs(context).proximityStartTime = System.currentTimeMillis()
}
fun proximityDidStop(context: Context) {
getSharedPrefs(context).edit {
putLong(SHARED_PREFS_PROXIMITY_ACTIVE_DURATION, getProximityActiveDuration(context))
remove(SHARED_PREFS_PROXIMITY_START_TIME)
getSharedPrefs(context).apply {
proximityActiveDuration = getProximityActiveDuration(context)
proximityStartTime = null
}
}
fun statusDidSucceed(context: Context) {
reportAppEvent(context, AppEventName.e16)
getSharedPrefs(context).edit {
putInt(SHARED_PREFS_STATUS_SUCCESS_COUNT, getSharedPrefs(context).getInt(SHARED_PREFS_STATUS_SUCCESS_COUNT, 0) + 1)
getSharedPrefs(context).apply {
statusSuccessCount += 1
}
}
private fun getProximityActiveDuration(context: Context): Long {
val oldDuration = getSharedPrefs(context).getLong(SHARED_PREFS_PROXIMITY_ACTIVE_DURATION, 0L)
val addedDuration = System.currentTimeMillis() - getSharedPrefs(context).getLong(
SHARED_PREFS_PROXIMITY_START_TIME,
System.currentTimeMillis()
)
val oldDuration = getSharedPrefs(context).proximityActiveDuration
val addedDuration = System.currentTimeMillis() - (getSharedPrefs(context).proximityStartTime ?: System.currentTimeMillis())
return oldDuration + addedDuration
}
......@@ -125,7 +123,7 @@ object AnalyticsManager : LifecycleObserver {
analyticsInfosProvider: AnalyticsInfosProvider,
token: String
) {
if (robertManager.configuration.isAnalyticsOn) {
if (robertManager.configuration.isAnalyticsOn && getSharedPrefs(context).isOptIn) {
val receivedHelloMessagesCount = robertManager.getLocalProximityCount()
sendAppAnalytics(context, analyticsInfosProvider, token, receivedHelloMessagesCount)
sendHealthAnalytics(context, robertManager, analyticsInfosProvider, token, receivedHelloMessagesCount)
......@@ -144,7 +142,7 @@ object AnalyticsManager : LifecycleObserver {
val appEvents = getAppEvents(context)
val appErrors = getErrors(context.filesDir)
val sendAnalyticsRQ = SendAnalyticsRQ(
installationUuid = sharedPreferences.getString(SHARED_PREFS_INSTALLATION_UUID, null) ?: UUID.randomUUID().toString(),
installationUuid = sharedPreferences.installationUUID ?: UUID.randomUUID().toString(),
infos = appInfos,
events = appEvents.toAPI(),
errors = appErrors.toAPI()
......@@ -223,35 +221,41 @@ object AnalyticsManager : LifecycleObserver {
@Suppress("BlockingMethodInNonBlockingContext")
@Synchronized
fun reportAppEvent(context: Context, eventName: AppEventName, desc: String? = null) {
CoroutineScope(Dispatchers.IO).launch {
val timestampedEventList = getAppEvents(context).toMutableList()
timestampedEventList += TimestampedEvent(eventName.name, dateFormat.format(Date()), desc ?: "")
val file = File(File(context.filesDir, FOLDER_NAME), FILE_NAME_APP_EVENTS)
writeTimestampedEventProtoToFile(file, timestampedEventList.toProto())
if (getSharedPrefs(context).isOptIn) {
CoroutineScope(Dispatchers.IO).launch {
val timestampedEventList = getAppEvents(context).toMutableList()
timestampedEventList += TimestampedEvent(eventName.name, dateFormat.format(Date()), desc ?: "")
val file = File(File(context.filesDir, FOLDER_NAME), FILE_NAME_APP_EVENTS)
writeTimestampedEventProtoToFile(file, timestampedEventList.toProto())
}
}
}
@Suppress("BlockingMethodInNonBlockingContext")
@Synchronized
fun reportHealthEvent(context: Context, eventName: HealthEventName, desc: String? = null) {
CoroutineScope(Dispatchers.IO).launch {
val timestampedEventList = getHealthEvents(context).toMutableList()
timestampedEventList += TimestampedEvent(eventName.name, dateFormat.format(Date()), desc ?: "")
val file = File(File(context.filesDir, FOLDER_NAME), FILE_NAME_HEALTH_EVENTS)
writeTimestampedEventProtoToFile(file, timestampedEventList.toProto())
if (getSharedPrefs(context).isOptIn) {
CoroutineScope(Dispatchers.IO).launch {
val timestampedEventList = getHealthEvents(context).toMutableList()
timestampedEventList += TimestampedEvent(eventName.name, dateFormat.format(Date()), desc ?: "")
val file = File(File(context.filesDir, FOLDER_NAME), FILE_NAME_HEALTH_EVENTS)
writeTimestampedEventProtoToFile(file, timestampedEventList.toProto())
}
}
}
@Suppress("BlockingMethodInNonBlockingContext")
@Synchronized
fun reportWSError(filesDir: File, wsName: String, wsVersion: String, errorCode: Int, desc: String? = null) {
if (desc?.contains("No address associated with hostname") != true) {
CoroutineScope(Dispatchers.IO).launch {
val name = "ERR-${wsName.toUpperCase(Locale.getDefault())}-${wsVersion.toUpperCase(Locale.getDefault())}-$errorCode"
val timestampedEventList = getErrors(filesDir).toMutableList()
timestampedEventList += TimestampedEvent(name, dateFormat.format(Date()), desc ?: "")
val file = File(File(filesDir, FOLDER_NAME), FILE_NAME_APP_ERRORS)
writeTimestampedEventProtoToFile(file, timestampedEventList.toProto())
fun reportWSError(context: Context, filesDir: File, wsName: String, wsVersion: String, errorCode: Int, desc: String? = null) {
if (getSharedPrefs(context).isOptIn) {
if (desc?.contains("No address associated with hostname") != true) {
CoroutineScope(Dispatchers.IO).launch {
val name = "ERR-${wsName.toUpperCase(Locale.getDefault())}-${wsVersion.toUpperCase(Locale.getDefault())}-$errorCode"
val timestampedEventList = getErrors(filesDir).toMutableList()
timestampedEventList += TimestampedEvent(name, dateFormat.format(Date()), desc ?: "")
val file = File(File(filesDir, FOLDER_NAME), FILE_NAME_APP_ERRORS)
writeTimestampedEventProtoToFile(file, timestampedEventList.toProto())
}
}
}
}
......@@ -280,7 +284,7 @@ object AnalyticsManager : LifecycleObserver {
placesCount = infosProvider.getPlacesCount(),
formsCount = infosProvider.getFormsCount(),
certificatesCount = infosProvider.getCertificatesCount(),
statusSuccessCount = sharedPreferences.getInt(SHARED_PREFS_STATUS_SUCCESS_COUNT, 0),
statusSuccessCount = sharedPreferences.statusSuccessCount ?: 0,
userHasAZipcode = infosProvider.userHaveAZipCode(),
)
}
......@@ -328,7 +332,12 @@ object AnalyticsManager : LifecycleObserver {
if (file.exists()) {
val atomicFile = AtomicFile(file)
atomicFile.openRead().use { inputStream ->
ProtoStorage.TimestampedEventProtoList.parseFrom(inputStream).toDomain()
try {
ProtoStorage.TimestampedEventProtoList.parseFrom(inputStream).toDomain()
} catch (e: Exception) {
Timber.e(e)
emptyList()
}
}
} else {
emptyList()
......
......@@ -43,7 +43,7 @@ import timber.log.Timber
import java.io.File
class ServiceDataSource(
context: Context,
private val context: Context,
baseUrl: String,
warningBaseUrl: String,
certificateSha256: String,
......@@ -190,7 +190,7 @@ class ServiceDataSource(
if (result.body()?.success == true) {
RobertResult.Success()
} else {
AnalyticsManager.reportWSError(filesDir, serviceName, apiVersion, result.code())
AnalyticsManager.reportWSError(context, filesDir, serviceName, apiVersion, result.code())
RobertResult.Failure(
BackendException(
result.body()?.message!!
......@@ -198,12 +198,12 @@ class ServiceDataSource(
)
}
} else {
AnalyticsManager.reportWSError(filesDir, serviceName, apiVersion, result.code())
AnalyticsManager.reportWSError(context, filesDir, serviceName, apiVersion, result.code())
RobertResult.Failure(HttpException(result).remoteToRobertException())
}
} catch (e: Exception) {
Timber.e(ServiceDataSource::class.java.simpleName, e.message ?: "")
AnalyticsManager.reportWSError(filesDir, serviceName, apiVersion, (e as? HttpException)?.code() ?: 0, e.message)
AnalyticsManager.reportWSError(context, filesDir, serviceName, apiVersion, (e as? HttpException)?.code() ?: 0, e.message)
RobertResult.Failure(error = e.remoteToRobertException())
}
}
......@@ -219,12 +219,12 @@ class ServiceDataSource(
if (result.isSuccessful) {
RobertResultData.Success(result.body()!!)
} else {
AnalyticsManager.reportWSError(filesDir, serviceName, apiVersion, result.code())
AnalyticsManager.reportWSError(context, filesDir, serviceName, apiVersion, result.code())
RobertResultData.Failure(HttpException(result).remoteToRobertException())
}
} catch (e: Exception) {
Timber.e(e)
AnalyticsManager.reportWSError(filesDir, serviceName, apiVersion, (e as? HttpException)?.code() ?: 0, e.message)
AnalyticsManager.reportWSError(context, filesDir, serviceName, apiVersion, (e as? HttpException)?.code() ?: 0, e.message)
RobertResultData.Failure(error = e.remoteToRobertException())
}
}
......
......@@ -43,8 +43,8 @@ android {
applicationId "fr.gouv.android.stopcovid"
minSdkVersion 21
targetSdkVersion 30
versionCode 214
versionName "2.6.0"
versionCode 216
versionName "2.6.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
......
......@@ -1060,6 +1060,11 @@
"wallet.proof.vaccinationCertificate.LA.EC": "On-going",
"wallet.proof.vaccinationCertificate.LA.TE": "Completed",
"wallet.proof.vaccinationCertificate.pillTitle": "Vaccine",
"wallet.proof.vaccinationCertificate.description": "<L1>, <L0>\nDate of birth: <L2>\n<L5> (<L4>)\nInjection date <L9>\nDose: <L7>/<L8>"
"wallet.proof.vaccinationCertificate.description": "<L1>, <L0>\nDate of birth: <L2>\n<L5> (<L4>)\nInjection date <L9>\nDose: <L7>/<L8>",
"manageDataController.analytics.title": "Statistiques et mesure d’audience",
"manageDataController.analytics.subtitle": "En conformité avec le RGPD, nous vous informons de notre capacité à collecter des données sur l’usage que vous faites de TousAntiCovid, à des fins de diagnostics, d’amélioration de performance et de l’expérience utilisateur. Sachez enfin que ces données sont conservées sur le serveur pendant 3 mois.",
"manageDataController.analytics.switch.on": "Activé",
"manageDataController.analytics.switch.off": "Désactivé"
}
......@@ -1060,6 +1060,11 @@
"wallet.proof.vaccinationCertificate.LA.EC": "On-going",
"wallet.proof.vaccinationCertificate.LA.TE": "Completed",
"wallet.proof.vaccinationCertificate.pillTitle": "Vaccine",
"wallet.proof.vaccinationCertificate.description": "<L1>, <L0>\nDate of birth: <L2>\n<L5> (<L4>)\nInjection date <L9>\nDose: <L7>/<L8>"
"wallet.proof.vaccinationCertificate.description": "<L1>, <L0>\nDate of birth: <L2>\n<L5> (<L4>)\nInjection date <L9>\nDose: <L7>/<L8>",
"manageDataController.analytics.title": "Statistiques et mesure d’audience",
"manageDataController.analytics.subtitle": "En conformité avec le RGPD, nous vous informons de notre capacité à collecter des données sur l’usage que vous faites de TousAntiCovid, à des fins de diagnostics, d’amélioration de performance et de l’expérience utilisateur. Sachez enfin que ces données sont conservées sur le serveur pendant 3 mois.",
"manageDataController.analytics.switch.on": "Activé",
"manageDataController.analytics.switch.off": "Désactivé"
}
......@@ -1060,6 +1060,11 @@
"wallet.proof.vaccinationCertificate.LA.EC": "On-going",
"wallet.proof.vaccinationCertificate.LA.TE": "Completed",
"wallet.proof.vaccinationCertificate.pillTitle": "Vaccine",
"wallet.proof.vaccinationCertificate.description": "<L1>, <L0>\nDate of birth: <L2>\n<L5> (<L4>)\nInjection date <L9>\nDose: <L7>/<L8>"
"wallet.proof.vaccinationCertificate.description": "<L1>, <L0>\nDate of birth: <L2>\n<L5> (<L4>)\nInjection date <L9>\nDose: <L7>/<L8>",
"manageDataController.analytics.title": "Statistiques et mesure d’audience",
"manageDataController.analytics.subtitle": "En conformité avec le RGPD, nous vous informons de notre capacité à collecter des données sur l’usage que vous faites de TousAntiCovid, à des fins de diagnostics, d’amélioration de performance et de l’expérience utilisateur. Sachez enfin que ces données sont conservées sur le serveur pendant 3 mois.",
"manageDataController.analytics.switch.on": "Activé",
"manageDataController.analytics.switch.off": "Désactivé"
}
......@@ -1060,6 +1060,11 @@
"wallet.proof.vaccinationCertificate.LA.EC": "On-going",
"wallet.proof.vaccinationCertificate.LA.TE": "Completed",
"wallet.proof.vaccinationCertificate.pillTitle": "Vaccine",
"wallet.proof.vaccinationCertificate.description": "<L1>, <L0>\nDate of birth: <L2>\n<L5> (<L4>)\nInjection date <L9>\nDose: <L7>/<L8>"
"wallet.proof.vaccinationCertificate.description": "<L1>, <L0>\nDate of birth: <L2>\n<L5> (<L4>)\nInjection date <L9>\nDose: <L7>/<L8>",
"manageDataController.analytics.title": "Statistiques et mesure d’audience",
"manageDataController.analytics.subtitle": "En conformité avec le RGPD, nous vous informons de notre capacité à collecter des données sur l’usage que vous faites de TousAntiCovid, à des fins de diagnostics, d’amélioration de performance et de l’expérience utilisateur. Sachez enfin que ces données sont conservées sur le serveur pendant 3 mois.",
"manageDataController.analytics.switch.on": "Activé",
"manageDataController.analytics.switch.off": "Désactivé"
}
......@@ -1049,6 +1049,11 @@
"wallet.proof.vaccinationCertificate.LA.EC": "En cours",
"wallet.proof.vaccinationCertificate.LA.TE": "Terminé",
"wallet.proof.vaccinationCertificate.pillTitle": "Vaccin",
"wallet.proof.vaccinationCertificate.description": "<L1>, <L0>\nné(e) le <L2>\n<L5> (<L4>)\ninjection le <L9>\nDose : <L7>/<L8>"
"wallet.proof.vaccinationCertificate.description": "<L1>, <L0>\nné(e) le <L2>\n<L5> (<L4>)\ninjection le <L9>\nDose : <L7>/<L8>",
"manageDataController.analytics.title": "Statistiques et mesure d’audience",
"manageDataController.analytics.subtitle": "En conformité avec le RGPD, nous vous informons de notre capacité à collecter des données sur l’usage que vous faites de TousAntiCovid, à des fins de diagnostics, d’amélioration de performance et de l’expérience utilisateur. Sachez enfin que ces données sont conservées sur le serveur pendant 3 mois.",
"manageDataController.analytics.switch.on": "Activé",
"manageDataController.analytics.switch.off": "Désactivé"
}
......@@ -1060,6 +1060,11 @@
"wallet.proof.vaccinationCertificate.LA.EC": "On-going",
"wallet.proof.vaccinationCertificate.LA.TE": "Completed",
"wallet.proof.vaccinationCertificate.pillTitle": "Vaccine",
"wallet.proof.vaccinationCertificate.description": "<L1>, <L0>\nDate of birth: <L2>\n<L5> (<L4>)\nInjection date <L9>\nDose: <L7>/<L8>"
"wallet.proof.vaccinationCertificate.description": "<L1>, <L0>\nDate of birth: <L2>\n<L5> (<L4>)\nInjection date <L9>\nDose: <L7>/<L8>",
"manageDataController.analytics.title": "Statistiques et mesure d’audience",
"manageDataController.analytics.subtitle": "En conformité avec le RGPD, nous vous informons de notre capacité à collecter des données sur l’usage que vous faites de TousAntiCovid, à des fins de diagnostics, d’amélioration de performance et de l’expérience utilisateur. Sachez enfin que ces données sont conservées sur le serveur pendant 3 mois.",
"manageDataController.analytics.switch.on": "Activé",
"manageDataController.analytics.switch.off": "Désactivé"
}
......@@ -1060,6 +1060,11 @@
"wallet.proof.vaccinationCertificate.LA.EC": "On-going",
"wallet.proof.vaccinationCertificate.LA.TE": "Completed",
"wallet.proof.vaccinationCertificate.pillTitle": "Vaccine",
"wallet.proof.vaccinationCertificate.description": "<L1>, <L0>\nDate of birth: <L2>\n<L5> (<L4>)\nInjection date <L9>\nDose: <L7>/<L8>"
"wallet.proof.vaccinationCertificate.description": "<L1>, <L0>\nDate of birth: <L2>\n<L5> (<L4>)\nInjection date <L9>\nDose: <L7>/<L8>",
"manageDataController.analytics.title": "Statistiques et mesure d’audience",
"manageDataController.analytics.subtitle": "En conformité avec le RGPD, nous vous informons de notre capacité à collecter des données sur l’usage que vous faites de TousAntiCovid, à des fins de diagnostics, d’amélioration de performance et de l’expérience utilisateur. Sachez enfin que ces données sont conservées sur le serveur pendant 3 mois.",
"manageDataController.analytics.switch.on": "Activé",
"manageDataController.analytics.switch.off": "Désactivé"
}
......@@ -17,6 +17,7 @@ import androidx.core.content.edit
import androidx.fragment.app.viewModels
import androidx.preference.PreferenceManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.lunabeestudio.analytics.manager.AnalyticsManager
import com.lunabeestudio.robert.RobertApplication
import com.lunabeestudio.stopcovid.Constants
import com.lunabeestudio.stopcovid.R
......@@ -129,6 +130,11 @@ class ManageDataFragment : MainFragment() {
spaceDividerItems(items)
}
if (robertManager.configuration.isAnalyticsOn) {
optOutAnalyticsItems(items)
spaceDividerItems(items)
}
if (deviceSetup != DeviceSetup.NO_BLE || robertManager.isRegistered) {
quitStopCovidItems(items)
}
......@@ -356,6 +362,36 @@ class ManageDataFragment : MainFragment() {
}
}
private fun optOutAnalyticsItems(items: MutableList<GenericItem>) {
items += titleItem {
text = strings["manageDataController.analytics.title"]
identifier = items.count().toLong()
}
items += spaceItem {
spaceRes = R.dimen.spacing_medium
}
items += captionItem {
text = strings["manageDataController.analytics.subtitle"]
identifier = items.count().toLong()
}
val isOptIn = context?.let(AnalyticsManager::isOptIn) ?: false
items += switchItem {
title = strings[if (isOptIn) {
"manageDataController.analytics.switch.on"
} else {
"manageDataController.analytics.switch.off"
}]
isChecked = isOptIn
onCheckChange = { isChecked ->
context?.let {
AnalyticsManager.setIsOptIn(it, isChecked)
refreshScreen()
}
}
identifier = items.count().toLong()
}
}
private fun quitStopCovidItems(items: MutableList<GenericItem>) {
items += titleItem {
text = strings["manageDataController.quitStopCovid.title"]
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment