Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion analytics/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ dependencies {
implementation(project(":core"))
implementation(project(":ui"))

implementation(libs.androidx.dataStore.preferences)
implementation(libs.androidx.datastore)
implementation(libs.protobuf.javalite)

implementation(platform(libs.firebase.bom))
implementation(libs.firebase.analytics)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,49 +31,30 @@

package no.nordicsemi.android.common.analytics.repository

import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.preferencesDataStore
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.map
import no.nordicsemi.android.common.analytics.AnalyticsPermissionData
import no.nordicsemi.android.common.core.settings.NordicCommonLibsSettingsRepository
import javax.inject.Inject
import javax.inject.Singleton

private const val FILE = "ANALYTICS_PERMISSION"
private const val HAS_BEEN_GRANTED = "HAS_BEEN_GRANTED"
private const val WAS_INFO_SHOWN = "WAS_INFO_SHOWN"

@Singleton
internal class AnalyticsPermissionRepository @Inject constructor(
@ApplicationContext private val context: Context
private val repo: NordicCommonLibsSettingsRepository
) {
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = FILE)

private val hasBeenGranted = booleanPreferencesKey(HAS_BEEN_GRANTED)
private val wasInfoShown = booleanPreferencesKey(WAS_INFO_SHOWN)

val permissionData = context.dataStore.data.map {
val permissionData = repo.nordicCommonLibsSettings.map {
AnalyticsPermissionData(
it[hasBeenGranted] ?: false,
it[wasInfoShown] ?: false
it.analyticsHasBeenGranted,
it.analyticsWasInfoShown
)
}

suspend fun onPermissionGranted() {
context.dataStore.edit {
it[hasBeenGranted] = true
it[wasInfoShown] = true
}
repo.updateAnalyticsHasBeenGranted(true)
repo.updateAnalyticsWasInfoShown(true)
}

suspend fun onPermissionDenied() {
context.dataStore.edit {
it[hasBeenGranted] = false
it[wasInfoShown] = true
}
repo.updateAnalyticsHasBeenGranted(false)
repo.updateAnalyticsWasInfoShown(true)
}
}
3 changes: 2 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ plugins {
alias(libs.plugins.hilt) apply false
alias(libs.plugins.kotlin.parcelize) apply false
alias(libs.plugins.compose.compiler) apply false

alias(libs.plugins.google.protobuf) apply false

// Nordic plugins are defined in https://github.com/NordicSemiconductor/Android-Gradle-Plugins
alias(libs.plugins.nordic.application.compose) apply false
alias(libs.plugins.nordic.library.compose) apply false
Expand Down
25 changes: 25 additions & 0 deletions core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ plugins {
alias(libs.plugins.nordic.library.compose)
alias(libs.plugins.nordic.nexus.android)
alias(libs.plugins.kotlin.parcelize)
alias(libs.plugins.google.protobuf)
}

group = "no.nordicsemi.android.common"
Expand Down Expand Up @@ -63,4 +64,28 @@ dokka {

dependencies {
implementation(libs.androidx.core)
implementation(libs.androidx.datastore)
implementation(libs.androidx.datastore.preferences)
implementation(libs.protobuf.javalite)
}

protobuf {
// Configures the Protobuf compilation and the protoc executable
protoc {
// Downloads from the repositories
artifact = "com.google.protobuf:protoc:4.31.1"
}

// Generates the java Protobuf-lite code for the Protobufs in this project
generateProtoTasks {
all().forEach {
it.builtins {
// Configures the task output type
create("java") {
// Java Lite has smaller code size and is recommended for Android
option("lite")
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package no.nordicsemi.android.common.core.settings

import android.content.Context
import android.util.Log
import androidx.datastore.core.DataStore
import androidx.datastore.dataStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import no.nordicsemi.android.common.core.settings.migrations.getMigrationsList
import java.io.IOException

private const val TAG: String = "NordicSettingsRepo"
private const val DATASTORE_FILENAME = "nordic_common_libs_settings.pb"
val Context.nordicCommonLibsSettingsDataStore: DataStore<NordicCommonLibsSettings>
by dataStore (
fileName = DATASTORE_FILENAME,
serializer = NordicCommonLibsSettingsSerializer,
produceMigrations = { context -> getMigrationsList(context) }
)

class NordicCommonLibsSettingsRepository private constructor(context: Context) {
private val dataStore = context.applicationContext.nordicCommonLibsSettingsDataStore

val nordicCommonLibsSettings: Flow<NordicCommonLibsSettings> = dataStore.data
.catch { exception ->
// dataStore.data throws an IOException when an error is encountered when reading data
if (exception is IOException) {
Log.e(TAG, "Error reading $DATASTORE_FILENAME.", exception)
emit(NordicCommonLibsSettings.getDefaultInstance())
} else {
throw exception
}
}

suspend fun updateLocationPermissionRequested(requested: Boolean) {
dataStore.updateData { settings ->
settings
.toBuilder()
.setLocationPermissionRequested(requested)
.build()
}
}

suspend fun updateBluetoothPermissionRequested(requested: Boolean) {
dataStore.updateData { settings ->
settings
.toBuilder()
.setBluetoothPermissionRequested(requested)
.build()
}
}

suspend fun updateWifiPermissionRequested(requested: Boolean) {
dataStore.updateData { settings ->
settings
.toBuilder()
.setWifiPermissionRequested(requested)
.build()
}
}

suspend fun updateNotificationPermissionRequested(requested: Boolean) {
dataStore.updateData { settings ->
settings
.toBuilder()
.setNotificationPermissionRequested(requested)
.build()
}
}

suspend fun updateAnalyticsHasBeenGranted(granted: Boolean) {
dataStore.updateData { settings ->
settings
.toBuilder()
.setAnalyticsHasBeenGranted(granted)
.build()
}
}

suspend fun updateAnalyticsWasInfoShown(shown: Boolean) {
dataStore.updateData { settings ->
settings
.toBuilder()
.setAnalyticsWasInfoShown(shown)
.build()
}
}

companion object {
private var _instance: NordicCommonLibsSettingsRepository? = null
fun getInstance(context: Context): NordicCommonLibsSettingsRepository {
if(_instance == null)
_instance = NordicCommonLibsSettingsRepository(context)
return _instance!!
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package no.nordicsemi.android.common.core.settings

import androidx.datastore.core.CorruptionException
import androidx.datastore.core.Serializer
import com.google.protobuf.InvalidProtocolBufferException
import java.io.InputStream
import java.io.OutputStream

object NordicCommonLibsSettingsSerializer : Serializer<NordicCommonLibsSettings> {
override val defaultValue: NordicCommonLibsSettings =
NordicCommonLibsSettings.newBuilder()
.setLocationPermissionRequested(false)
.setBluetoothPermissionRequested(false)
.setNotificationPermissionRequested(false)
.setWifiPermissionRequested(false)
.build()

override suspend fun readFrom(input: InputStream): NordicCommonLibsSettings {
try {
return NordicCommonLibsSettings.parseFrom(input)
} catch (exception: InvalidProtocolBufferException) {
throw CorruptionException("Cannot read proto.", exception)
}
}

override suspend fun writeTo(t: NordicCommonLibsSettings, output: OutputStream) =
t.writeTo(output)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package no.nordicsemi.android.common.core.settings.migrations

import android.content.Context
import androidx.datastore.core.DataMigration
import androidx.datastore.core.DataStore
import androidx.datastore.migrations.SharedPreferencesMigration
import androidx.datastore.migrations.SharedPreferencesView
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.first
import no.nordicsemi.android.common.core.settings.NordicCommonLibsSettings
import java.io.File

private const val SHARED_PREFS_NAME_BLE_WIFI = "SHARED_PREFS_NAME"
private const val PREFS_PERMISSION_REQUESTED = "permission_requested"
private const val PREFS_BLUETOOTH_PERMISSION_REQUESTED = "bluetooth_permission_requested"
private const val PREFS_WIFI_PERMISSION_REQUESTED = "wifi_permission_requested"
private const val SHARED_PREFS_NAME_NOTIFICATIONS = "SHARED_PREFS_NOTIFICATION"
private const val PREFS_NOTIFICATION_PERMISSION_REQUESTED = "notification_permission_requested"
private const val DATASTORE_ANALYTICS_PREFERENCES_FILENAME = "ANALYTICS_PERMISSION"
private const val DATASTORE_ANALYTICS_HAS_BEEN_GRANTED = "HAS_BEEN_GRANTED"
private const val DATASTORE_ANALYTICS_WAS_INFO_SHOWN = "WAS_INFO_SHOWN"
private val Context.analyticsDataStore: DataStore<Preferences>
by preferencesDataStore(name = DATASTORE_ANALYTICS_PREFERENCES_FILENAME)

fun getMigrationsList(context: Context): List<DataMigration<NordicCommonLibsSettings>> {
return listOf(
SharedPreferencesMigration(
context,
sharedPreferencesName = SHARED_PREFS_NAME_BLE_WIFI,
keysToMigrate = setOf(
PREFS_PERMISSION_REQUESTED,
PREFS_BLUETOOTH_PERMISSION_REQUESTED,
PREFS_WIFI_PERMISSION_REQUESTED
)
) { sharedPrefs: SharedPreferencesView, currentData: NordicCommonLibsSettings ->
// Define the mapping from SharedPreferences to Settings proto-datastore
currentData
.toBuilder()
.setLocationPermissionRequested(
sharedPrefs
.getBoolean(PREFS_PERMISSION_REQUESTED, false)
)
.setBluetoothPermissionRequested(
sharedPrefs
.getBoolean(PREFS_BLUETOOTH_PERMISSION_REQUESTED, false)
)
.setWifiPermissionRequested(
sharedPrefs
.getBoolean(PREFS_WIFI_PERMISSION_REQUESTED, false)
)
.build()
},
SharedPreferencesMigration(
context,
sharedPreferencesName = SHARED_PREFS_NAME_NOTIFICATIONS,
keysToMigrate = setOf(
PREFS_NOTIFICATION_PERMISSION_REQUESTED
)
) { sharedPrefs: SharedPreferencesView, currentData: NordicCommonLibsSettings ->
// Define the mapping from SharedPreferences to Settings proto-datastore
currentData
.toBuilder()
.setNotificationPermissionRequested(
sharedPrefs
.getBoolean(PREFS_NOTIFICATION_PERMISSION_REQUESTED, false)
)
.build()
},
object : DataMigration<NordicCommonLibsSettings> {
private val dataStoreFile =
File(context.filesDir,
"datastore/$DATASTORE_ANALYTICS_PREFERENCES_FILENAME.preferences_pb"
)

override suspend fun shouldMigrate(currentData: NordicCommonLibsSettings): Boolean {
return dataStoreFile.exists()
}

override suspend fun migrate(
currentData: NordicCommonLibsSettings
): NordicCommonLibsSettings {
// Access old Preferences DataStore
val oldPreferences = context.analyticsDataStore.data.first()
return currentData
.toBuilder()
.setAnalyticsWasInfoShown(
oldPreferences[booleanPreferencesKey(DATASTORE_ANALYTICS_WAS_INFO_SHOWN)]
?: false
)
.setAnalyticsHasBeenGranted(
oldPreferences[booleanPreferencesKey(DATASTORE_ANALYTICS_HAS_BEEN_GRANTED)]
?: false
)
.build()
}

override suspend fun cleanUp() {
dataStoreFile.delete()
}
}
)
}
14 changes: 14 additions & 0 deletions core/src/main/proto/NordicCommonLibSettings.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
syntax = "proto3";

option java_package = "no.nordicsemi.android.common.core.settings";
option java_multiple_files = true;

message NordicCommonLibsSettings {

bool location_permission_requested = 1;
bool bluetooth_permission_requested = 2;
bool wifi_permission_requested = 3;
bool notification_permission_requested = 4;
bool analytics_has_been_granted = 5;
bool analytics_was_info_shown = 6;
}
Loading