Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into reteno-master
Browse files Browse the repository at this point in the history
  • Loading branch information
mykhailoantiufieievatreteno committed Apr 30, 2024
2 parents f52fc04 + a626f99 commit 53bdb7a
Show file tree
Hide file tree
Showing 36 changed files with 623 additions and 294 deletions.
1 change: 0 additions & 1 deletion RetenoSdkCore/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ dependencies {
implementation "androidx.core:core-ktx:1.9.0"
implementation 'androidx.cardview:cardview:1.0.0'
compileOnly "com.google.firebase:firebase-messaging-ktx:23.1.2"
implementation "net.zetetic:android-database-sqlcipher:4.5.2"
implementation "androidx.sqlite:sqlite:2.1.0"

implementation 'androidx.work:work-runtime-ktx:2.8.1'
Expand Down
3 changes: 0 additions & 3 deletions RetenoSdkCore/consumer-rules.pro
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
-keep class net.sqlcipher.** { *; }
-keep class net.sqlcipher.database.** { *; }


### Gson ProGuard and R8 rules which are relevant for all users
### This file is automatically recognized by ProGuard and R8, see https://developer.android.com/build/shrink-code#configuration-files
Expand Down
6 changes: 5 additions & 1 deletion RetenoSdkCore/src/main/java/com/reteno/core/RetenoConfig.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package com.reteno.core

import com.reteno.core.identification.DeviceIdProvider

/**
* @param isPausedInAppMessages - indicates paused/resumed state for in-app messages
* @param userIdProvider Provider that will return custom userId. In case if id provided with a delay,
* Reteno SDK will wait till id is going to be non-null then will initialize itself
* @property platform - current platform name (Note that this property is mutable for multiplatform usage
* and it should not be changed in other use cases).
*
* */
class RetenoConfig @JvmOverloads constructor(
var isPausedInAppMessages: Boolean = false,
val userIdProvider: DeviceIdProvider? = null
) {
var platform: String = "Android"
}
58 changes: 41 additions & 17 deletions RetenoSdkCore/src/main/java/com/reteno/core/RetenoImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,17 @@ import com.reteno.core.util.Constants.BROADCAST_ACTION_PUSH_PERMISSION_CHANGED
import com.reteno.core.util.Constants.BROADCAST_ACTION_RETENO_APP_RESUME
import com.reteno.core.view.iam.IamView
import com.reteno.core.view.iam.callback.InAppLifecycleCallback
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch


class RetenoImpl @JvmOverloads constructor(
class RetenoImpl internal constructor(
application: Application,
accessKey: String,
config: RetenoConfig = RetenoConfig(),
config: RetenoConfig,
private val asyncScope: CoroutineScope
) : RetenoLifecycleCallbacks, Reteno {

init {
Expand All @@ -33,36 +38,37 @@ class RetenoImpl @JvmOverloads constructor(
Companion.application = application
}

val serviceLocator: ServiceLocator = ServiceLocator(application, accessKey, config.platform)
val serviceLocator: ServiceLocator =
ServiceLocator(application, accessKey, config.platform, config.userIdProvider)
private val activityHelper: RetenoActivityHelper by lazy { serviceLocator.retenoActivityHelperProvider.get() }

private val screenTrackingController: ScreenTrackingController by lazy { serviceLocator.screenTrackingControllerProvider.get() }
private val contactController by lazy { serviceLocator.contactControllerProvider.get() }
private val scheduleController by lazy { serviceLocator.scheduleControllerProvider.get() }
private val eventController by lazy { serviceLocator.eventsControllerProvider.get() }
private val iamController by lazy { serviceLocator.iamControllerProvider.get() }
private val sessionHandler by lazy { serviceLocator.retenoSessionHandlerProvider.get() }
private val sessionHandler by lazy { serviceLocator.retenoSessionHandlerProvider.get() }

override val appInbox by lazy { serviceLocator.appInboxProvider.get() }
override val recommendation by lazy { serviceLocator.recommendationProvider.get() }
private val iamView: IamView by lazy { serviceLocator.iamViewProvider.get() }

init {
if (isOsVersionSupported()) {
try {
activityHelper.enableLifecycleCallbacks(this)
clearOldData()
contactController.checkIfDeviceRegistered()
sendAppResumeBroadcast()
pauseInAppMessages(config.isPausedInAppMessages)
fetchInAppMessages()
} catch (t: Throwable) {
/*@formatter:off*/ Logger.e(TAG, "init(): ", t)
/*@formatter:on*/
}
}
initSdk(config)
}

@JvmOverloads
constructor(
application: Application,
accessKey: String,
config: RetenoConfig = RetenoConfig()
) : this(
application = application,
accessKey = accessKey,
config = config,
asyncScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
)

override fun start(activity: Activity) {
if (!isOsVersionSupported()) {
return
Expand Down Expand Up @@ -285,6 +291,24 @@ class RetenoImpl @JvmOverloads constructor(
}
}

private fun initSdk(config: RetenoConfig) {
if (isOsVersionSupported()) {
activityHelper.enableLifecycleCallbacks(this@RetenoImpl)
clearOldData()
asyncScope.launch {
try {
contactController.checkIfDeviceRegistered()
sendAppResumeBroadcast()
pauseInAppMessages(config.isPausedInAppMessages)
fetchInAppMessages()
} catch (t: Throwable) {
/*@formatter:off*/ Logger.e(TAG, "init(): ", t)
/*@formatter:on*/
}
}
}
}

private fun clearOldData() {
scheduleController.clearOldData()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,22 @@ import android.provider.Settings
import com.google.android.gms.appset.AppSet
import com.reteno.core.RetenoImpl
import com.reteno.core.data.local.sharedpref.SharedPrefsManager
import com.reteno.core.identification.DeviceIdProvider
import com.reteno.core.util.Logger
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

internal class DeviceIdHelper(private val sharedPrefsManager: SharedPrefsManager) {
internal class DeviceIdHelper(
private val sharedPrefsManager: SharedPrefsManager,
private val userIdProvider: DeviceIdProvider?
) {

private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())

internal fun withDeviceIdMode(
currentDeviceId: DeviceId,
Expand All @@ -25,13 +38,14 @@ internal class DeviceIdHelper(private val sharedPrefsManager: SharedPrefsManager
/*@formatter:off*/ Logger.i(TAG, "initDeviceId(): ", "deviceIdMode = [", deviceIdMode, "]", " deviceId = [", deviceId, "]")
/*@formatter:on*/
onDeviceIdChanged(currentDeviceId.copy(id = deviceId, mode = deviceIdMode))
} catch (ex: java.lang.Exception) {
} catch (ex: Exception) {
/*@formatter:off*/ Logger.e(TAG, "initDeviceId(): DeviceIdMode.ANDROID_ID", ex)
/*@formatter:on*/
withDeviceIdMode(currentDeviceId, DeviceIdMode.RANDOM_UUID, onDeviceIdChanged)
return
}
}

DeviceIdMode.APP_SET_ID -> {
try {
val client = AppSet.getClient(context)
Expand All @@ -42,20 +56,63 @@ internal class DeviceIdHelper(private val sharedPrefsManager: SharedPrefsManager
}.addOnFailureListener {
/*@formatter:off*/ Logger.i(TAG, "initDeviceId(): ", "deviceIdMode = [", deviceIdMode, "]", " failed trying ANDROID_ID")
/*@formatter:on*/
withDeviceIdMode(currentDeviceId, DeviceIdMode.RANDOM_UUID, onDeviceIdChanged)
withDeviceIdMode(
currentDeviceId,
DeviceIdMode.RANDOM_UUID,
onDeviceIdChanged
)
}
} catch (ex: java.lang.Exception) {
} catch (ex: Exception) {
/*@formatter:off*/ Logger.e(TAG, "initDeviceId(): DeviceIdMode.APP_SET_ID", ex)
/*@formatter:on*/
withDeviceIdMode(currentDeviceId, DeviceIdMode.RANDOM_UUID, onDeviceIdChanged)
}
}

DeviceIdMode.RANDOM_UUID -> {
val newId = sharedPrefsManager.getDeviceIdUuid()
onDeviceIdChanged(currentDeviceId.copy(id = newId, mode = deviceIdMode))
/*@formatter:off*/ Logger.i(TAG, "initDeviceId(): ", "deviceIdMode = [", deviceIdMode, "]", " deviceId = [", newId, "]")
/*@formatter:on*/
}

DeviceIdMode.CLIENT_UUID -> {
if (userIdProvider == null) {
/*@formatter:off*/ Logger.i(TAG, "initDeviceId(): ", "deviceIdMode = [", deviceIdMode, "]", " failed trying DeviceIdProvider is null")
/*@formatter:on*/
withDeviceIdMode(
currentDeviceId,
DeviceIdMode.RANDOM_UUID,
onDeviceIdChanged
)
return
}
scope.launch {
try {
var deviceId: String? = null
while (deviceId == null && isActive) {
deviceId = userIdProvider.getDeviceId()
delay(60L)
}
withContext(Dispatchers.Main) {
/*@formatter:off*/ Logger.i(TAG, "initDeviceId(): ", "deviceIdMode = [", deviceIdMode, "]", " deviceId = [", deviceId, "]")
/*@formatter:on*/
onDeviceIdChanged(
currentDeviceId.copy(
id = requireNotNull(deviceId),
mode = deviceIdMode
)
)
}
} catch (e: Exception) {
withDeviceIdMode(
currentDeviceId,
DeviceIdMode.RANDOM_UUID,
onDeviceIdChanged
)
}
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.reteno.core.data.local.config

import com.reteno.core.RetenoConfig
import com.reteno.core.identification.DeviceIdProvider

enum class DeviceIdMode {

/**
Expand Down Expand Up @@ -28,5 +31,12 @@ enum class DeviceIdMode {
* Generates a random UUID and saves it to local storage.
* Is rotated on app storage clean
*/
RANDOM_UUID
RANDOM_UUID,

/**
* Uses the Id provided by user
*
* Uses [DeviceIdProvider] from [RetenoConfig] as an Id provider
*/
CLIENT_UUID
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@ package com.reteno.core.data.local.config

import com.reteno.core.util.Logger

internal class RestConfig(private val deviceIdHelper: DeviceIdHelper, internal val accessKey: String) {
internal class RestConfig(
private val deviceIdHelper: DeviceIdHelper,
internal val accessKey: String,
initIdMode: DeviceIdMode
) {

internal var deviceId: DeviceId = DeviceId("")
private set

init {
initDeviceId(DeviceIdMode.ANDROID_ID)
initDeviceId(initIdMode)
}

private fun initDeviceId(deviceIdMode: DeviceIdMode) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import android.database.sqlite.SQLiteException
import android.database.sqlite.SQLiteOpenHelper
import android.os.SystemClock
import android.util.Log
import com.reteno.core.BuildConfig
import com.reteno.core.data.local.database.schema.AppInboxSchema
import com.reteno.core.data.local.database.schema.DbSchema.DATABASE_NAME
import com.reteno.core.data.local.database.schema.DbSchema.DATABASE_VERSION
Expand All @@ -25,41 +24,36 @@ import com.reteno.core.data.local.database.schema.RecomEventsSchema
import com.reteno.core.data.local.database.schema.UserSchema
import com.reteno.core.data.local.database.schema.WrappedLinkSchema
import com.reteno.core.util.Logger
import com.reteno.core.util.SqlStateEncrypt
import com.reteno.core.util.Util
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.runBlocking
import java.io.IOException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch

internal class RetenoDatabaseImpl(private val context: Context) : RetenoDatabase,
SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {

//TODO Delete this code when instructed to remove the sqlcipher library.
private val writableSQLDatabase: SQLiteDatabase by lazy { writableDatabase }
private val coroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())

init {
runBlocking(IO) {
if (Util.getDatabaseState(
context,
context.getDatabasePath(DATABASE_NAME)
) == SqlStateEncrypt.ENCRYPTED
) {
try {
Util.decrypt(
context,
context.getDatabasePath(DATABASE_NAME),
BuildConfig.SQL_PASSWORD.toByteArray()
)
/*@formatter:off*/ Logger.d(TAG, "RetenoDatabaseImpl.init{}" , "DB converted from sqlCipher")
dropDatabaseIfEncrypted()
}

private fun dropDatabaseIfEncrypted() {
coroutineScope.launch {
synchronized(LOCK) {
if (Util.isEncryptedDatabase(context, DATABASE_NAME)) {
/*@formatter:off*/ Logger.i(TAG, "dropDatabaseIfEncrypted(): ", "Database was encrypted. Dropping database...")
/*@formatter:on*/
} catch (ioe: IOException) {
/*@formatter:off*/ Logger.e(TAG, "RetenoDatabaseImpl.init{}" , ioe)
context.deleteDatabase(DATABASE_NAME)
/*@formatter:off*/ Logger.i(TAG, "dropDatabaseIfEncrypted(): ", "Database deleted.")
/*@formatter:on*/
}
}
}
}

private val writableSQLDatabase: SQLiteDatabase by lazy { writableDatabase }

override fun onOpen(db: SQLiteDatabase?) {
/*@formatter:off*/ Logger.i(TAG, "onOpen(): ", "db = [" , db , "]")
/*@formatter:on*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.reteno.core.data.local.config.DeviceId
interface ConfigRepository {
fun setExternalUserId(externalId: String?)
fun getDeviceId(): DeviceId
suspend fun awaitForDeviceId(): DeviceId
fun saveFcmToken(token: String)
fun getFcmToken(callback: (String) -> Unit)
fun saveDefaultNotificationChannel(channel: String)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import com.reteno.core.data.local.config.DeviceId
import com.reteno.core.data.local.config.RestConfig
import com.reteno.core.data.local.sharedpref.SharedPrefsManager
import com.reteno.core.util.Logger
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlin.coroutines.coroutineContext


internal class ConfigRepositoryImpl(
Expand All @@ -19,6 +22,13 @@ internal class ConfigRepositoryImpl(

override fun getDeviceId(): DeviceId = restConfig.deviceId

override suspend fun awaitForDeviceId(): DeviceId {
while (coroutineContext.isActive && restConfig.deviceId.id == "") {
delay(30L)
}
return restConfig.deviceId
}

override fun saveFcmToken(token: String) {
sharedPrefsManager.saveFcmToken(token)
}
Expand Down

0 comments on commit 53bdb7a

Please sign in to comment.