From 5c70469e35479ceebba048949a6b65786d8cf355 Mon Sep 17 00:00:00 2001 From: Muhammad Rehan Date: Wed, 16 Oct 2024 21:19:23 +0500 Subject: [PATCH] feat: support for wrapper SDKs (#442) Co-authored-by: Shahroz Khan --- common-test/src/main/AndroidManifest.xml | 15 +++- .../commontest/config/TestArgument.kt | 8 -- .../io/customer/commontest/core/BaseTest.kt | 6 +- .../commontest/util/UnitTestLogger.kt | 3 + core/api/core.api | 12 ++- core/src/androidTest/AndroidManifest.xml | 22 +++++ .../data/store/AndroidManifestClientTest.kt | 15 ++++ .../sdk/core/di/AndroidSDKComponent.kt | 7 +- .../kotlin/io/customer/sdk/core/di/DiGraph.kt | 14 --- .../customer/sdk/core/di/SDKComponentExt.kt | 10 +-- .../sdk/core/extensions/ContextExtensions.kt | 31 +++++++ .../io/customer/sdk/core/util/Logger.kt | 80 ++++++++++++----- .../io/customer/sdk/data/store/Client.kt | 88 ++++++------------- .../customer/sdk/core/util/CioLogLevelTest.kt | 54 ++++++++++++ .../io/customer/sdk/core/util/LoggerTest.kt | 85 +++++++++++++++++- .../io/customer/sdk/data/store/ClientTest.kt | 85 ++++++++++++++++++ .../main/kotlin/io/customer/sdk/CustomerIO.kt | 36 ++++++-- .../io/customer/sdk/CustomerIOBuilder.kt | 8 +- .../io/customer/sdk/DataPipelineInstance.kt | 18 ++-- .../DataPipelinesInteractionTests.kt | 14 +++ messaginginapp/api/messaginginapp.api | 1 + .../di/DIGraphMessagingInApp.kt | 6 +- .../testutils/core/IntegrationTest.kt | 2 - .../testutils/core/JUnitTest.kt | 2 - messagingpush/api/messagingpush.api | 4 + .../CustomerIOCloudMessagingReceiver.kt | 3 +- .../CustomerIOFirebaseMessagingService.kt | 4 +- .../CustomerIOPushNotificationHandler.kt | 17 +--- .../NotificationClickReceiverActivity.kt | 2 + .../messagingpush/di/DiGraphMessagingPush.kt | 6 +- .../testutils/core/IntegrationTest.kt | 2 - .../messagingpush/testutils/core/JUnitTest.kt | 2 - .../testutils/core/IntegrationTest.kt | 2 - .../migration/testutils/core/JUnitTest.kt | 2 - 34 files changed, 485 insertions(+), 181 deletions(-) create mode 100644 core/src/androidTest/AndroidManifest.xml create mode 100644 core/src/androidTest/java/io/customer/sdk/data/store/AndroidManifestClientTest.kt create mode 100644 core/src/main/kotlin/io/customer/sdk/core/extensions/ContextExtensions.kt create mode 100644 core/src/test/java/io/customer/sdk/core/util/CioLogLevelTest.kt create mode 100644 core/src/test/java/io/customer/sdk/data/store/ClientTest.kt diff --git a/common-test/src/main/AndroidManifest.xml b/common-test/src/main/AndroidManifest.xml index a5918e68a..151d37163 100644 --- a/common-test/src/main/AndroidManifest.xml +++ b/common-test/src/main/AndroidManifest.xml @@ -1,4 +1,17 @@ - \ No newline at end of file + + + + + + diff --git a/common-test/src/main/java/io/customer/commontest/config/TestArgument.kt b/common-test/src/main/java/io/customer/commontest/config/TestArgument.kt index dd571c805..4e4fa3645 100644 --- a/common-test/src/main/java/io/customer/commontest/config/TestArgument.kt +++ b/common-test/src/main/java/io/customer/commontest/config/TestArgument.kt @@ -1,7 +1,6 @@ package io.customer.commontest.config import android.app.Application -import io.customer.sdk.data.store.Client /** * Base interface for all test arguments. @@ -17,10 +16,3 @@ interface TestArgument data class ApplicationArgument( val value: Application ) : TestArgument - -/** - * Argument for passing client instance to test configuration. - */ -data class ClientArgument( - val value: Client = Client.Android(sdkVersion = "3.0.0") -) : TestArgument diff --git a/common-test/src/main/java/io/customer/commontest/core/BaseTest.kt b/common-test/src/main/java/io/customer/commontest/core/BaseTest.kt index d90aec018..5930283a0 100644 --- a/common-test/src/main/java/io/customer/commontest/core/BaseTest.kt +++ b/common-test/src/main/java/io/customer/commontest/core/BaseTest.kt @@ -1,14 +1,13 @@ package io.customer.commontest.core import io.customer.commontest.config.ApplicationArgument -import io.customer.commontest.config.ClientArgument import io.customer.commontest.config.TestConfig import io.customer.commontest.config.argumentOrNull import io.customer.commontest.config.configureAndroidSDKComponent import io.customer.commontest.config.configureSDKComponent import io.customer.commontest.config.testConfigurationDefault import io.customer.sdk.core.di.SDKComponent -import io.customer.sdk.core.di.registerAndroidSDKComponent +import io.customer.sdk.core.di.setupAndroidComponent import io.mockk.clearAllMocks /** @@ -43,8 +42,7 @@ abstract class BaseTest { */ private fun registerAndroidSDKComponent(testConfig: TestConfig) { val application = testConfig.argumentOrNull()?.value ?: return - val client = testConfig.argumentOrNull()?.value ?: return - testConfig.configureAndroidSDKComponent(SDKComponent.registerAndroidSDKComponent(application, client)) + testConfig.configureAndroidSDKComponent(SDKComponent.setupAndroidComponent(application)) } } diff --git a/common-test/src/main/java/io/customer/commontest/util/UnitTestLogger.kt b/common-test/src/main/java/io/customer/commontest/util/UnitTestLogger.kt index dfa7e0fa4..affae4012 100644 --- a/common-test/src/main/java/io/customer/commontest/util/UnitTestLogger.kt +++ b/common-test/src/main/java/io/customer/commontest/util/UnitTestLogger.kt @@ -11,6 +11,9 @@ import io.customer.sdk.core.util.Logger class UnitTestLogger : Logger { override var logLevel: CioLogLevel = CioLogLevel.DEBUG + override fun setLogDispatcher(dispatcher: ((CioLogLevel, String) -> Unit)?) { + } + override fun info(message: String) { log(CioLogLevel.INFO, message) } diff --git a/core/api/core.api b/core/api/core.api index e4b8d96ea..cc28582e6 100644 --- a/core/api/core.api +++ b/core/api/core.api @@ -122,7 +122,7 @@ public abstract class io/customer/sdk/core/di/AndroidSDKComponent : io/customer/ } public final class io/customer/sdk/core/di/AndroidSDKComponentImpl : io/customer/sdk/core/di/AndroidSDKComponent { - public fun (Landroid/content/Context;Lio/customer/sdk/data/store/Client;)V + public fun (Landroid/content/Context;)V public fun getApplication ()Landroid/app/Application; public fun getApplicationContext ()Landroid/content/Context; public fun getApplicationStore ()Lio/customer/sdk/data/store/ApplicationStore; @@ -137,7 +137,6 @@ public abstract class io/customer/sdk/core/di/DiGraph { public fun ()V public final fun getOverrides ()Ljava/util/concurrent/ConcurrentHashMap; public final fun getSingletons ()Ljava/util/concurrent/ConcurrentHashMap; - public final fun overrideDependency (Ljava/lang/Class;Ljava/lang/Object;)V public fun reset ()V } @@ -155,7 +154,7 @@ public final class io/customer/sdk/core/di/SDKComponent : io/customer/sdk/core/d } public final class io/customer/sdk/core/di/SDKComponentExtKt { - public static final fun registerAndroidSDKComponent (Lio/customer/sdk/core/di/SDKComponent;Landroid/content/Context;Lio/customer/sdk/data/store/Client;)Lio/customer/sdk/core/di/AndroidSDKComponent; + public static final fun setupAndroidComponent (Lio/customer/sdk/core/di/SDKComponent;Landroid/content/Context;)Lio/customer/sdk/core/di/AndroidSDKComponent; } public abstract interface class io/customer/sdk/core/environment/BuildEnvironment { @@ -167,6 +166,10 @@ public final class io/customer/sdk/core/environment/DefaultBuildEnvironment : io public fun getDebugModeEnabled ()Z } +public final class io/customer/sdk/core/extensions/ContextExtensionsKt { + public static final fun applicationMetaData (Landroid/content/Context;)Landroid/os/Bundle; +} + public abstract interface class io/customer/sdk/core/module/CustomerIOModule { public abstract fun getModuleConfig ()Lio/customer/sdk/core/module/CustomerIOModuleConfig; public abstract fun getModuleName ()Ljava/lang/String; @@ -186,6 +189,7 @@ public final class io/customer/sdk/core/util/CioLogLevel : java/lang/Enum { public static final field ERROR Lio/customer/sdk/core/util/CioLogLevel; public static final field INFO Lio/customer/sdk/core/util/CioLogLevel; public static final field NONE Lio/customer/sdk/core/util/CioLogLevel; + public final fun getPriority ()I public static fun valueOf (Ljava/lang/String;)Lio/customer/sdk/core/util/CioLogLevel; public static fun values ()[Lio/customer/sdk/core/util/CioLogLevel; } @@ -210,6 +214,7 @@ public final class io/customer/sdk/core/util/LogcatLogger : io/customer/sdk/core public fun error (Ljava/lang/String;)V public fun getLogLevel ()Lio/customer/sdk/core/util/CioLogLevel; public fun info (Ljava/lang/String;)V + public fun setLogDispatcher (Lkotlin/jvm/functions/Function2;)V public fun setLogLevel (Lio/customer/sdk/core/util/CioLogLevel;)V } @@ -221,6 +226,7 @@ public abstract interface class io/customer/sdk/core/util/Logger { public abstract fun error (Ljava/lang/String;)V public abstract fun getLogLevel ()Lio/customer/sdk/core/util/CioLogLevel; public abstract fun info (Ljava/lang/String;)V + public abstract fun setLogDispatcher (Lkotlin/jvm/functions/Function2;)V public abstract fun setLogLevel (Lio/customer/sdk/core/util/CioLogLevel;)V } diff --git a/core/src/androidTest/AndroidManifest.xml b/core/src/androidTest/AndroidManifest.xml new file mode 100644 index 000000000..491c1f203 --- /dev/null +++ b/core/src/androidTest/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + + + diff --git a/core/src/androidTest/java/io/customer/sdk/data/store/AndroidManifestClientTest.kt b/core/src/androidTest/java/io/customer/sdk/data/store/AndroidManifestClientTest.kt new file mode 100644 index 000000000..be2cf9873 --- /dev/null +++ b/core/src/androidTest/java/io/customer/sdk/data/store/AndroidManifestClientTest.kt @@ -0,0 +1,15 @@ +package io.customer.sdk.data.store + +import io.customer.commontest.core.AndroidTest +import io.customer.sdk.core.extensions.applicationMetaData +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test + +class AndroidManifestClientTest : AndroidTest() { + @Test + fun fromManifest_givenTestMetaData_expectClientWithTestMetaData() { + val client = Client.fromMetadata(application.applicationMetaData()) + + client.toString() shouldBeEqualTo "TestUserAgent Client/1.3.5" + } +} diff --git a/core/src/main/kotlin/io/customer/sdk/core/di/AndroidSDKComponent.kt b/core/src/main/kotlin/io/customer/sdk/core/di/AndroidSDKComponent.kt index 97569b8a2..44635e274 100644 --- a/core/src/main/kotlin/io/customer/sdk/core/di/AndroidSDKComponent.kt +++ b/core/src/main/kotlin/io/customer/sdk/core/di/AndroidSDKComponent.kt @@ -2,6 +2,7 @@ package io.customer.sdk.core.di import android.app.Application import android.content.Context +import io.customer.sdk.core.extensions.applicationMetaData import io.customer.sdk.data.store.ApplicationStore import io.customer.sdk.data.store.ApplicationStoreImpl import io.customer.sdk.data.store.BuildStore @@ -28,12 +29,14 @@ abstract class AndroidSDKComponent : DiGraph() { * Integrate this graph at SDK startup using from Android entry point. */ class AndroidSDKComponentImpl( - private val context: Context, - override val client: Client + private val context: Context ) : AndroidSDKComponent() { override val application: Application get() = newInstance { context.applicationContext as Application } + override val client: Client + get() = singleton { Client.fromMetadata(context.applicationMetaData()) } + init { SDKComponent.activityLifecycleCallbacks.register(application) } diff --git a/core/src/main/kotlin/io/customer/sdk/core/di/DiGraph.kt b/core/src/main/kotlin/io/customer/sdk/core/di/DiGraph.kt index b16a766ef..7a243dcbb 100644 --- a/core/src/main/kotlin/io/customer/sdk/core/di/DiGraph.kt +++ b/core/src/main/kotlin/io/customer/sdk/core/di/DiGraph.kt @@ -133,18 +133,4 @@ abstract class DiGraph { overrides.clear() singletons.clear() } - - // TODO: Remove deprecated functions after all usages are removed. - @Deprecated("Use overrideDependency instead", ReplaceWith("overrideDependency(value)")) - fun overrideDependency(dependency: Class, value: Dependency) { - overrides[dependency.name] = value as Any - } - - @Deprecated("Use newInstance or singleton instead", ReplaceWith("newInstance()")) - inline fun override(): DEP? = overrides[dependencyKey(identifier = null)] as? DEP - - @Deprecated("Use singleton instead", ReplaceWith("singleton(newInstanceCreator)")) - inline fun getSingletonInstanceCreate(newInstanceCreator: () -> INST): INST { - return getOrCreateSingletonInstance(identifier = null, newInstanceCreator = newInstanceCreator) - } } diff --git a/core/src/main/kotlin/io/customer/sdk/core/di/SDKComponentExt.kt b/core/src/main/kotlin/io/customer/sdk/core/di/SDKComponentExt.kt index df57e86af..38be83737 100644 --- a/core/src/main/kotlin/io/customer/sdk/core/di/SDKComponentExt.kt +++ b/core/src/main/kotlin/io/customer/sdk/core/di/SDKComponentExt.kt @@ -1,7 +1,6 @@ package io.customer.sdk.core.di import android.content.Context -import io.customer.sdk.data.store.Client /** * The file contains extension functions for the SDKComponent object and its dependencies. @@ -10,10 +9,11 @@ import io.customer.sdk.data.store.Client /** * Create and register an instance of AndroidSDKComponent with the provided context, * only if it is not already initialized. + * This function should be called from all entry points of the SDK to ensure that + * AndroidSDKComponent is initialized before accessing any of its dependencies. */ -fun SDKComponent.registerAndroidSDKComponent( - context: Context, - client: Client +fun SDKComponent.setupAndroidComponent( + context: Context ) = registerDependency { - AndroidSDKComponentImpl(context, client) + AndroidSDKComponentImpl(context) } diff --git a/core/src/main/kotlin/io/customer/sdk/core/extensions/ContextExtensions.kt b/core/src/main/kotlin/io/customer/sdk/core/extensions/ContextExtensions.kt new file mode 100644 index 000000000..52754f159 --- /dev/null +++ b/core/src/main/kotlin/io/customer/sdk/core/extensions/ContextExtensions.kt @@ -0,0 +1,31 @@ +package io.customer.sdk.core.extensions + +import android.content.Context +import android.content.pm.PackageManager +import android.os.Build +import android.os.Bundle +import io.customer.sdk.core.di.SDKComponent + +/** + * Retrieves application meta-data from AndroidManifest.xml file. + * + * @return The meta-data bundle from application info. + */ +fun Context.applicationMetaData(): Bundle? = try { + val applicationInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + packageManager.getApplicationInfo( + packageName, + PackageManager.ApplicationInfoFlags.of(PackageManager.GET_META_DATA.toLong()) + ) + } else { + @Suppress("DEPRECATION") + packageManager.getApplicationInfo( + packageName, + PackageManager.GET_META_DATA + ) + } + applicationInfo.metaData +} catch (ex: Exception) { + SDKComponent.logger.error("Failed to get ApplicationInfo with error: ${ex.message}") + null +} diff --git a/core/src/main/kotlin/io/customer/sdk/core/util/Logger.kt b/core/src/main/kotlin/io/customer/sdk/core/util/Logger.kt index bc39ea19d..fe8c912bb 100644 --- a/core/src/main/kotlin/io/customer/sdk/core/util/Logger.kt +++ b/core/src/main/kotlin/io/customer/sdk/core/util/Logger.kt @@ -4,34 +4,59 @@ import android.util.Log import io.customer.sdk.core.environment.BuildEnvironment interface Logger { + // Log level to determine which logs to print + // This is the log level set by the user in configurations or the default log level var logLevel: CioLogLevel + + /** + * Sets the dispatcher to handle log events based on the log level + * Default implementation is to print logs to Logcat + * In wrapper SDKs, this will be overridden to emit logs to more user-friendly channels + * like console, etc. + * If the dispatcher holds any reference to application context, the caller should ensure + * to clear references when the context is destroyed. + * + * @param dispatcher Dispatcher to handle log events based on the log level, pass null + * to reset to default + */ + fun setLogDispatcher(dispatcher: ((CioLogLevel, String) -> Unit)?) + fun info(message: String) fun debug(message: String) fun error(message: String) } -enum class CioLogLevel { - NONE, - ERROR, - INFO, - DEBUG; +/** + * Log levels for Customer.io SDK logs + * + * @property priority Priority of the log level. The higher the value, the more verbose + * the log level. + * @see shouldLog to determine if a log should be printed based on specified log level + */ +enum class CioLogLevel(val priority: Int) { + NONE(priority = 0), + ERROR(priority = 1), + INFO(priority = 2), + DEBUG(priority = 3); companion object { val DEFAULT = ERROR - fun getLogLevel(level: String?, fallback: CioLogLevel = NONE): CioLogLevel { + + fun getLogLevel(level: String?, fallback: CioLogLevel = DEFAULT): CioLogLevel { return values().find { value -> value.name.equals(level, ignoreCase = true) } ?: fallback } } } +/** + * Determines if a log should be printed based on the specified log level + * + * @param levelForMessage Log level of the message + * @return true if the log should be printed, false otherwise + */ internal fun CioLogLevel.shouldLog(levelForMessage: CioLogLevel): Boolean { - return when (this) { - CioLogLevel.NONE -> false - CioLogLevel.ERROR -> levelForMessage == CioLogLevel.ERROR - CioLogLevel.INFO -> levelForMessage == CioLogLevel.ERROR || levelForMessage == CioLogLevel.INFO - CioLogLevel.DEBUG -> true - } + return this.priority >= levelForMessage.priority } class LogcatLogger( @@ -54,28 +79,37 @@ class LogcatLogger( preferredLogLevel = value } + private var logDispatcher: ((CioLogLevel, String) -> Unit)? = null + + override fun setLogDispatcher(dispatcher: ((CioLogLevel, String) -> Unit)?) { + logDispatcher = dispatcher + } + override fun info(message: String) { - runIfMeetsLogLevelCriteria(CioLogLevel.INFO) { - Log.i(TAG, message) - } + logIfMatchesCriteria(CioLogLevel.INFO, message) } override fun debug(message: String) { - runIfMeetsLogLevelCriteria(CioLogLevel.DEBUG) { - Log.d(TAG, message) - } + logIfMatchesCriteria(CioLogLevel.DEBUG, message) } override fun error(message: String) { - runIfMeetsLogLevelCriteria(CioLogLevel.ERROR) { - Log.e(TAG, message) - } + logIfMatchesCriteria(CioLogLevel.ERROR, message) } - private fun runIfMeetsLogLevelCriteria(levelForMessage: CioLogLevel, block: () -> Unit) { + private fun logIfMatchesCriteria(levelForMessage: CioLogLevel, message: String) { val shouldLog = logLevel.shouldLog(levelForMessage) - if (shouldLog) block() + if (shouldLog) { + // Dispatch log event to log dispatcher only if the log level is met and the dispatcher is set + // Otherwise, log to Logcat + logDispatcher?.invoke(levelForMessage, message) ?: when (levelForMessage) { + CioLogLevel.NONE -> {} + CioLogLevel.ERROR -> Log.e(TAG, message) + CioLogLevel.INFO -> Log.i(TAG, message) + CioLogLevel.DEBUG -> Log.d(TAG, message) + } + } } companion object { diff --git a/core/src/main/kotlin/io/customer/sdk/data/store/Client.kt b/core/src/main/kotlin/io/customer/sdk/data/store/Client.kt index e78cac963..2d44cc330 100644 --- a/core/src/main/kotlin/io/customer/sdk/data/store/Client.kt +++ b/core/src/main/kotlin/io/customer/sdk/data/store/Client.kt @@ -1,83 +1,45 @@ package io.customer.sdk.data.store +import android.os.Bundle +import io.customer.sdk.Version + /** - * Sealed class to hold information about the SDK wrapper and package that the - * client app is using. + * Represents the client information to append with user-agent. * * @property source name of the client to append with user-agent. * @property sdkVersion version of the SDK used. */ -sealed class Client( +@Suppress("MemberVisibilityCanBePrivate") +class Client( val source: String, val sdkVersion: String ) { override fun toString(): String = "$source Client/$sdkVersion" - /** - * Simpler class for Android clients. - */ - class Android(sdkVersion: String) : Client(source = SOURCE_ANDROID, sdkVersion = sdkVersion) - - /** - * Simpler class for ReactNative clients. - */ - class ReactNative(sdkVersion: String) : Client( - source = SOURCE_REACT_NATIVE, - sdkVersion = sdkVersion - ) - - /** - * Simpler class for Expo clients. - */ - class Expo(sdkVersion: String) : Client(source = SOURCE_EXPO, sdkVersion = sdkVersion) - - /** - * Simpler class for Flutter clients. - */ - class Flutter(sdkVersion: String) : Client(source = SOURCE_FLUTTER, sdkVersion = sdkVersion) - - /** - * Other class to allow adding custom sources for clients that are not - * supported above. - *

- * Use this only if the client platform is not available in the above list. - */ - class Other internal constructor( - source: String, - sdkVersion: String - ) : Client(source = source, sdkVersion = sdkVersion) - companion object { - internal const val SOURCE_ANDROID = "Android" - internal const val SOURCE_REACT_NATIVE = "ReactNative" - internal const val SOURCE_EXPO = "Expo" - internal const val SOURCE_FLUTTER = "Flutter" + private const val SOURCE_ANDROID = "Android" + internal const val META_DATA_SDK_SOURCE = "io.customer.sdk.android.core.SDK_SOURCE" + internal const val META_DATA_SDK_VERSION = "io.customer.sdk.android.core.SDK_VERSION" /** - * Helper method to create client from raw values + * Creates a new [Client] instance from the manifest meta-data. + * If the user-agent or SDK version is not found, the default client is returned. + * Default client is created with [SOURCE_ANDROID] and SDK version mentioned in [Version] class. * - * @param source raw string of client source (case insensitive) - * @param sdkVersion version of the SDK used - * @return [Client] created from provided values + * @param metadata Android application meta-data to retrieve the user-agent and SDK version from. + * @return The client instance created from the manifest meta-data. + * If not found, the default client is returned. */ - fun fromRawValue(source: String, sdkVersion: String): Client = when { - source.equals( - other = SOURCE_ANDROID, - ignoreCase = true - ) -> Android(sdkVersion = sdkVersion) - source.equals( - other = SOURCE_REACT_NATIVE, - ignoreCase = true - ) -> ReactNative(sdkVersion = sdkVersion) - source.equals( - other = SOURCE_EXPO, - ignoreCase = true - ) -> Expo(sdkVersion = sdkVersion) - source.equals( - other = SOURCE_FLUTTER, - ignoreCase = true - ) -> Flutter(sdkVersion = sdkVersion) - else -> Other(source = source, sdkVersion = sdkVersion) + fun fromMetadata(metadata: Bundle?): Client { + val userAgent = metadata?.getString(META_DATA_SDK_SOURCE) + val sdkVersion = metadata?.getString(META_DATA_SDK_VERSION) + + // If either value is null or blank, return the default client + return if (userAgent.isNullOrBlank() || sdkVersion.isNullOrBlank()) { + Client(source = SOURCE_ANDROID, sdkVersion = Version.version) + } else { + Client(source = userAgent, sdkVersion = sdkVersion) + } } } } diff --git a/core/src/test/java/io/customer/sdk/core/util/CioLogLevelTest.kt b/core/src/test/java/io/customer/sdk/core/util/CioLogLevelTest.kt new file mode 100644 index 000000000..5ccf7cb13 --- /dev/null +++ b/core/src/test/java/io/customer/sdk/core/util/CioLogLevelTest.kt @@ -0,0 +1,54 @@ +package io.customer.sdk.core.util + +import io.customer.commontest.core.JUnit5Test +import org.amshove.kluent.shouldBeEqualTo +import org.junit.jupiter.api.Test + +class CioLogLevelTest : JUnit5Test() { + @Test + fun getLogLevel_givenNamesWithMatchingCase_expectCorrectLogLevel() { + val logLevelNone = CioLogLevel.getLogLevel("NONE") + val logLevelError = CioLogLevel.getLogLevel("ERROR") + val logLevelInfo = CioLogLevel.getLogLevel("INFO") + val logLevelDebug = CioLogLevel.getLogLevel("DEBUG") + + logLevelNone shouldBeEqualTo CioLogLevel.NONE + logLevelError shouldBeEqualTo CioLogLevel.ERROR + logLevelInfo shouldBeEqualTo CioLogLevel.INFO + logLevelDebug shouldBeEqualTo CioLogLevel.DEBUG + } + + @Test + fun getLogLevel_givenNamesWithDifferentCase_expectCorrectLogLevel() { + val logLevelNone = CioLogLevel.getLogLevel("None") + val logLevelError = CioLogLevel.getLogLevel("eRROR") + val logLevelInfo = CioLogLevel.getLogLevel("info") + val logLevelDebug = CioLogLevel.getLogLevel("DeBuG") + + logLevelNone shouldBeEqualTo CioLogLevel.NONE + logLevelError shouldBeEqualTo CioLogLevel.ERROR + logLevelInfo shouldBeEqualTo CioLogLevel.INFO + logLevelDebug shouldBeEqualTo CioLogLevel.DEBUG + } + + @Test + fun getLogLevel_givenInvalidValue_expectFallbackLogLevel() { + val parsedLogLevel = CioLogLevel.getLogLevel("invalid") + + parsedLogLevel shouldBeEqualTo CioLogLevel.ERROR + } + + @Test + fun getLogLevel_givenEmptyValue_expectFallbackLogLevel() { + val parsedLogLevel = CioLogLevel.getLogLevel("") + + parsedLogLevel shouldBeEqualTo CioLogLevel.ERROR + } + + @Test + fun getLogLevel_givenNull_expectFallbackLogLevel() { + val parsedLogLevel = CioLogLevel.getLogLevel(null) + + parsedLogLevel shouldBeEqualTo CioLogLevel.ERROR + } +} diff --git a/core/src/test/java/io/customer/sdk/core/util/LoggerTest.kt b/core/src/test/java/io/customer/sdk/core/util/LoggerTest.kt index db2dbf513..04689a270 100644 --- a/core/src/test/java/io/customer/sdk/core/util/LoggerTest.kt +++ b/core/src/test/java/io/customer/sdk/core/util/LoggerTest.kt @@ -1,16 +1,18 @@ package io.customer.sdk.core.util import io.customer.commontest.core.JUnit5Test +import io.customer.commontest.extensions.assertCalledNever +import io.customer.commontest.extensions.assertCalledOnce +import io.customer.commontest.extensions.assertNoInteractions import io.customer.sdk.core.environment.BuildEnvironment +import io.mockk.confirmVerified import io.mockk.every import io.mockk.mockk +import io.mockk.spyk import org.amshove.kluent.shouldBeEqualTo import org.junit.jupiter.api.Test class LoggerTest : JUnit5Test() { - - // Test log levels - @Test fun shouldLog_givenNone() { val configLogLevelSet = CioLogLevel.NONE @@ -113,4 +115,81 @@ class LoggerTest : JUnit5Test() { logger.logLevel shouldBeEqualTo givenLogLevel } + + @Test + fun logIfMatchesCriteria_givenLogLevelNone_shouldNotInvokeAnyLogs() { + val logger = spyk(LogcatLogger(mockk(relaxed = true))) + val logEventListenerMock = mockk<(CioLogLevel, String) -> Unit>(relaxed = true) + logger.logLevel = CioLogLevel.NONE + logger.setLogDispatcher(logEventListenerMock) + + val givenErrorMessage = "Test error message" + logger.error(givenErrorMessage) + val givenInfoMessage = "Test info message" + logger.info(givenInfoMessage) + val givenDebugMessage = "Test debug message" + logger.debug(givenDebugMessage) + + assertNoInteractions(logEventListenerMock) + } + + @Test + fun logIfMatchesCriteria_givenLogLevelError_shouldInvokeErrorLogOnly() { + val logger = spyk(LogcatLogger(mockk(relaxed = true))) + val logEventListenerMock = mockk<(CioLogLevel, String) -> Unit>(relaxed = true) + logger.logLevel = CioLogLevel.ERROR + logger.setLogDispatcher(logEventListenerMock) + + val givenErrorMessage = "Test error message" + logger.error(givenErrorMessage) + val givenInfoMessage = "Test info message" + logger.info(givenInfoMessage) + val givenDebugMessage = "Test debug message" + logger.debug(givenDebugMessage) + + assertCalledOnce { logEventListenerMock(CioLogLevel.ERROR, givenErrorMessage) } + assertCalledNever { logEventListenerMock(CioLogLevel.INFO, givenInfoMessage) } + assertCalledNever { logEventListenerMock(CioLogLevel.DEBUG, givenDebugMessage) } + confirmVerified(logEventListenerMock) + } + + @Test + fun logIfMatchesCriteria_givenLogLevelInfo_shouldInvokeInfoAndErrorLogs() { + val logger = spyk(LogcatLogger(mockk(relaxed = true))) + val logEventListenerMock = mockk<(CioLogLevel, String) -> Unit>(relaxed = true) + logger.logLevel = CioLogLevel.INFO + logger.setLogDispatcher(logEventListenerMock) + + val givenErrorMessage = "Test error message" + logger.error(givenErrorMessage) + val givenInfoMessage = "Test info message" + logger.info(givenInfoMessage) + val givenDebugMessage = "Test debug message" + logger.debug(givenDebugMessage) + + assertCalledOnce { logEventListenerMock(CioLogLevel.ERROR, givenErrorMessage) } + assertCalledOnce { logEventListenerMock(CioLogLevel.INFO, givenInfoMessage) } + assertCalledNever { logEventListenerMock(CioLogLevel.DEBUG, givenDebugMessage) } + confirmVerified(logEventListenerMock) + } + + @Test + fun logIfMatchesCriteria_givenLogLevelDebug_shouldInvokeAllLogs() { + val logger = spyk(LogcatLogger(mockk(relaxed = true))) + val logEventListenerMock = mockk<(CioLogLevel, String) -> Unit>(relaxed = true) + logger.logLevel = CioLogLevel.DEBUG + logger.setLogDispatcher(logEventListenerMock) + + val givenErrorMessage = "Test error message" + logger.error(givenErrorMessage) + val givenInfoMessage = "Test info message" + logger.info(givenInfoMessage) + val givenDebugMessage = "Test debug message" + logger.debug(givenDebugMessage) + + assertCalledOnce { logEventListenerMock(CioLogLevel.ERROR, givenErrorMessage) } + assertCalledOnce { logEventListenerMock(CioLogLevel.INFO, givenInfoMessage) } + assertCalledOnce { logEventListenerMock(CioLogLevel.DEBUG, givenDebugMessage) } + confirmVerified(logEventListenerMock) + } } diff --git a/core/src/test/java/io/customer/sdk/data/store/ClientTest.kt b/core/src/test/java/io/customer/sdk/data/store/ClientTest.kt new file mode 100644 index 000000000..3b31c2529 --- /dev/null +++ b/core/src/test/java/io/customer/sdk/data/store/ClientTest.kt @@ -0,0 +1,85 @@ +package io.customer.sdk.data.store + +import android.os.Bundle +import io.customer.commontest.core.RobolectricTest +import io.customer.sdk.Version +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class ClientTest : RobolectricTest() { + private val defaultClientString: String = "Android Client/${Version.version}" + + private fun createMetadata( + userAgent: String?, + sdkVersion: String? + ) = Bundle().apply { + userAgent?.let { putString(Client.META_DATA_SDK_SOURCE, it) } + sdkVersion?.let { putString(Client.META_DATA_SDK_VERSION, it) } + } + + @Test + fun fromManifest_givenValidMetaData_expectClientWithMetaData() { + val metadata = createMetadata("ReactNative", "1.2.3") + + val client = Client.fromMetadata(metadata) + + client.toString() shouldBeEqualTo "ReactNative Client/1.2.3" + } + + @Test + fun fromManifest_givenNullUserAgent_expectDefaultSourceUsed() { + val metadata = createMetadata(null, "1.2.3") + + val client = Client.fromMetadata(metadata) + + client.toString() shouldBeEqualTo defaultClientString + } + + @Test + fun fromManifest_givenEmptyUserAgent_expectDefaultSourceUsed() { + val metadata = createMetadata("", "1.2.3") + + val client = Client.fromMetadata(metadata) + + client.toString() shouldBeEqualTo defaultClientString + } + + @Test + fun fromManifest_givenNullSdkVersion_expectDefaultSdkVersionUsed() { + val metadata = createMetadata("ReactNative", null) + + val client = Client.fromMetadata(metadata) + + client.toString() shouldBeEqualTo defaultClientString + } + + @Test + fun fromManifest_givenEmptySdkVersion_expectDefaultSdkVersionUsed() { + val metadata = createMetadata("ReactNative", "") + + val client = Client.fromMetadata(metadata) + + client.toString() shouldBeEqualTo defaultClientString + } + + @Test + fun fromManifest_givenNullMetaData_expectDefaultValuesUsed() { + val metadata = createMetadata(null, null) + + val client = Client.fromMetadata(metadata) + + client.toString() shouldBeEqualTo defaultClientString + } + + @Test + fun fromManifest_givenEmptyMetaData_expectDefaultValuesUsed() { + val metadata = createMetadata("", "") + + val client = Client.fromMetadata(metadata) + + client.toString() shouldBeEqualTo defaultClientString + } +} diff --git a/datapipelines/src/main/kotlin/io/customer/sdk/CustomerIO.kt b/datapipelines/src/main/kotlin/io/customer/sdk/CustomerIO.kt index 737a2f972..5d4dd2450 100644 --- a/datapipelines/src/main/kotlin/io/customer/sdk/CustomerIO.kt +++ b/datapipelines/src/main/kotlin/io/customer/sdk/CustomerIO.kt @@ -4,8 +4,11 @@ import androidx.annotation.VisibleForTesting import com.segment.analytics.kotlin.android.Analytics import com.segment.analytics.kotlin.core.Analytics import com.segment.analytics.kotlin.core.ErrorHandler +import com.segment.analytics.kotlin.core.emptyJsonObject +import com.segment.analytics.kotlin.core.platform.EnrichmentClosure import com.segment.analytics.kotlin.core.platform.plugins.logger.LogKind import com.segment.analytics.kotlin.core.platform.plugins.logger.LogMessage +import com.segment.analytics.kotlin.core.utilities.JsonAnySerializer import io.customer.base.internal.InternalCustomerIOApi import io.customer.datapipelines.config.DataPipelinesModuleConfig import io.customer.datapipelines.di.analyticsFactory @@ -30,6 +33,7 @@ import io.customer.sdk.data.model.CustomAttributes import io.customer.sdk.events.TrackMetric import io.customer.tracking.migration.MigrationProcessor import kotlinx.serialization.SerializationStrategy +import kotlinx.serialization.serializer /** * Welcome to the Customer.io Android SDK! @@ -224,9 +228,14 @@ class CustomerIO private constructor( * Common method to track an event with traits. * All other track methods should call this method to ensure consistency. */ - override fun track(name: String, properties: T, serializationStrategy: SerializationStrategy) { + override fun track(name: String, properties: T, serializationStrategy: SerializationStrategy) = track(name, properties, serializationStrategy, null) + + /** + * Private method that support enrichment of generated track events. + */ + private fun track(name: String, properties: T, serializationStrategy: SerializationStrategy, enrichment: EnrichmentClosure?) { logger.debug("track an event with name $name and attributes $properties") - analytics.track(name = name, properties = properties, serializationStrategy = serializationStrategy) + analytics.track(name = name, properties = properties, serializationStrategy = serializationStrategy, enrichment = enrichment) } /** @@ -242,7 +251,13 @@ class CustomerIO private constructor( logger.info("resetting user profile with id ${this.userId}") logger.debug("deleting device token to remove device from user profile") - deleteDeviceToken() + + // since the tasks are asynchronous, we need to store the userId before deleting the device token + // otherwise, the userId could be null when the delete task is executed + val existingUserId = userId + deleteDeviceToken { event -> + event?.apply { userId = existingUserId.toString() } + } logger.debug("resetting user profile") analytics.reset() @@ -272,7 +287,7 @@ class CustomerIO private constructor( logger.info("storing and registering device token $deviceToken for user profile: ${this.userId}") globalPreferenceStore.saveDeviceToken(deviceToken) - trackDeviceAttributes(deviceToken) + trackDeviceAttributes(token = deviceToken) } private fun trackDeviceAttributes(token: String?, customAddedAttributes: CustomAttributes = emptyMap()) { @@ -298,10 +313,15 @@ class CustomerIO private constructor( contextPlugin.deviceToken = token logger.info("updating device attributes: $attributes") - track(EventNames.DEVICE_UPDATE, attributes) + track( + name = EventNames.DEVICE_UPDATE, + properties = attributes + ) } - override fun deleteDeviceToken() { + override fun deleteDeviceToken() = deleteDeviceToken(null) + + private fun deleteDeviceToken(enrichment: EnrichmentClosure?) { logger.info("deleting device token") val deviceToken = contextPlugin.deviceToken @@ -310,14 +330,14 @@ class CustomerIO private constructor( return } - track(EventNames.DEVICE_DELETE) + track(name = EventNames.DEVICE_DELETE, properties = emptyJsonObject, serializationStrategy = JsonAnySerializer.serializersModule.serializer(), enrichment = enrichment) } override fun trackMetric(event: TrackMetric) { logger.info("${event.type} metric received for ${event.metric} event") logger.debug("tracking ${event.type} metric event with properties $event") - track(EventNames.METRIC_DELIVERY, event.asMap()) + track(name = EventNames.METRIC_DELIVERY, properties = event.asMap()) } companion object { diff --git a/datapipelines/src/main/kotlin/io/customer/sdk/CustomerIOBuilder.kt b/datapipelines/src/main/kotlin/io/customer/sdk/CustomerIOBuilder.kt index c19833fd0..e8a3ee86e 100644 --- a/datapipelines/src/main/kotlin/io/customer/sdk/CustomerIOBuilder.kt +++ b/datapipelines/src/main/kotlin/io/customer/sdk/CustomerIOBuilder.kt @@ -4,13 +4,12 @@ import android.app.Application import com.segment.analytics.kotlin.core.platform.policies.FlushPolicy import io.customer.datapipelines.config.DataPipelinesModuleConfig import io.customer.sdk.core.di.SDKComponent -import io.customer.sdk.core.di.registerAndroidSDKComponent +import io.customer.sdk.core.di.setupAndroidComponent import io.customer.sdk.core.module.CustomerIOModule import io.customer.sdk.core.module.CustomerIOModuleConfig import io.customer.sdk.core.util.CioLogLevel import io.customer.sdk.core.util.Logger import io.customer.sdk.data.model.Region -import io.customer.sdk.data.store.Client /** * Creates a new instance of builder for CustomerIO SDK. @@ -34,9 +33,8 @@ class CustomerIOBuilder( // Initialize AndroidSDKComponent as soon as the builder is created so that // it can be used by the modules. // Also, it is needed to override test dependencies in the test environment - private val androidSDKComponent = SDKComponent.registerAndroidSDKComponent( - context = applicationContext, - client = Client.Android(Version.version) + private val androidSDKComponent = SDKComponent.setupAndroidComponent( + context = applicationContext ) // List of modules to be initialized with the SDK diff --git a/datapipelines/src/main/kotlin/io/customer/sdk/DataPipelineInstance.kt b/datapipelines/src/main/kotlin/io/customer/sdk/DataPipelineInstance.kt index 3999b8811..ce61271e8 100644 --- a/datapipelines/src/main/kotlin/io/customer/sdk/DataPipelineInstance.kt +++ b/datapipelines/src/main/kotlin/io/customer/sdk/DataPipelineInstance.kt @@ -31,7 +31,7 @@ abstract class DataPipelineInstance : CustomerIOInstance { * @param traits [Traits] about the user. Needs to be [serializable](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serializers.md) */ inline fun identify(userId: String, traits: Traits) { - identify(userId, traits, JsonAnySerializer.serializersModule.serializer()) + identify(userId = userId, traits = traits, serializationStrategy = JsonAnySerializer.serializersModule.serializer()) } /** @@ -47,7 +47,7 @@ abstract class DataPipelineInstance : CustomerIOInstance { */ @JvmOverloads fun identify(userId: String, traits: JsonObject = emptyJsonObject) { - identify(userId, traits, JsonAnySerializer.serializersModule.serializer()) + identify(userId = userId, traits = traits, serializationStrategy = JsonAnySerializer.serializersModule.serializer()) } /** @@ -63,7 +63,7 @@ abstract class DataPipelineInstance : CustomerIOInstance { */ fun identify(userId: String, traits: CustomAttributes) { // Method needed for Java interop as inline doesn't work with Java - identify(userId, traits, JsonAnySerializer.serializersModule.serializer()) + identify(userId = userId, traits = traits, serializationStrategy = JsonAnySerializer.serializersModule.serializer()) } /** @@ -98,7 +98,7 @@ abstract class DataPipelineInstance : CustomerIOInstance { */ @JvmOverloads fun track(name: String, properties: JsonObject = emptyJsonObject) { - track(name, properties, JsonAnySerializer.serializersModule.serializer()) + track(name = name, properties = properties, serializationStrategy = JsonAnySerializer.serializersModule.serializer()) } /** @@ -114,7 +114,7 @@ abstract class DataPipelineInstance : CustomerIOInstance { */ fun track(name: String, properties: CustomAttributes) { // Method needed for Java interop as inline doesn't work with Java - track(name, properties, JsonAnySerializer.serializersModule.serializer()) + track(name = name, properties = properties, serializationStrategy = JsonAnySerializer.serializersModule.serializer()) } /** @@ -150,7 +150,7 @@ abstract class DataPipelineInstance : CustomerIOInstance { name: String, properties: T ) { - track(name, properties, JsonAnySerializer.serializersModule.serializer()) + track(name = name, properties = properties, serializationStrategy = JsonAnySerializer.serializersModule.serializer()) } /** @@ -162,7 +162,7 @@ abstract class DataPipelineInstance : CustomerIOInstance { */ @JvmOverloads fun screen(title: String, properties: JsonObject = emptyJsonObject) { - screen(title, properties, JsonAnySerializer.serializersModule.serializer()) + screen(title = title, properties = properties, serializationStrategy = JsonAnySerializer.serializersModule.serializer()) } /** @@ -174,7 +174,7 @@ abstract class DataPipelineInstance : CustomerIOInstance { */ fun screen(title: String, properties: CustomAttributes) { // Method needed for Java interop as inline doesn't work with Java - screen(title, properties, JsonAnySerializer.serializersModule.serializer()) + screen(title = title, properties = properties, serializationStrategy = JsonAnySerializer.serializersModule.serializer()) } /** @@ -201,7 +201,7 @@ abstract class DataPipelineInstance : CustomerIOInstance { title: String, properties: T ) { - screen(title, properties, JsonAnySerializer.serializersModule.serializer()) + screen(title = title, properties = properties, serializationStrategy = JsonAnySerializer.serializersModule.serializer()) } /** diff --git a/datapipelines/src/test/java/io/customer/datapipelines/DataPipelinesInteractionTests.kt b/datapipelines/src/test/java/io/customer/datapipelines/DataPipelinesInteractionTests.kt index 02a6314a5..cf4fe81fd 100644 --- a/datapipelines/src/test/java/io/customer/datapipelines/DataPipelinesInteractionTests.kt +++ b/datapipelines/src/test/java/io/customer/datapipelines/DataPipelinesInteractionTests.kt @@ -597,6 +597,20 @@ class DataPipelinesInteractionTests : JUnitTest() { sdkInstance.registeredDeviceToken shouldBeEqualTo givenToken } + @Test + fun device_givenDeleteDeviceWithIdentifiedUser_expectUserIdWithTrackRequest() { + val givenIdentifier = String.random + + every { globalPreferenceStore.getDeviceToken() } returns String.random + + sdkInstance.identify(givenIdentifier) + + sdkInstance.clearIdentify() + + val event = outputReaderPlugin.trackEvents.last() + event.userId shouldBeEqualTo givenIdentifier + } + @Test fun device_givenDeleteDeviceWhenNoExistingPushToken_expectNoEventDispatched() { sdkInstance.deleteDeviceToken() diff --git a/messaginginapp/api/messaginginapp.api b/messaginginapp/api/messaginginapp.api index 9714d7f80..0c4ea5402 100644 --- a/messaginginapp/api/messaginginapp.api +++ b/messaginginapp/api/messaginginapp.api @@ -45,6 +45,7 @@ public final class io/customer/messaginginapp/databinding/ActivityGistBinding : } public final class io/customer/messaginginapp/di/DIGraphMessagingInAppKt { + public static final fun getInAppModuleConfig (Lio/customer/sdk/core/di/SDKComponent;)Lio/customer/messaginginapp/MessagingInAppModuleConfig; public static final fun inAppMessaging (Lio/customer/sdk/CustomerIOInstance;)Lio/customer/messaginginapp/ModuleMessagingInApp; } diff --git a/messaginginapp/src/main/java/io/customer/messaginginapp/di/DIGraphMessagingInApp.kt b/messaginginapp/src/main/java/io/customer/messaginginapp/di/DIGraphMessagingInApp.kt index 5749cca73..f9254782c 100644 --- a/messaginginapp/src/main/java/io/customer/messaginginapp/di/DIGraphMessagingInApp.kt +++ b/messaginginapp/src/main/java/io/customer/messaginginapp/di/DIGraphMessagingInApp.kt @@ -15,14 +15,14 @@ import io.customer.sdk.core.di.SDKComponent internal val SDKComponent.gistQueue: GistQueue get() = singleton { Queue() } -internal val SDKComponent.moduleConfig: MessagingInAppModuleConfig +val SDKComponent.inAppModuleConfig: MessagingInAppModuleConfig get() = inAppMessaging.moduleConfig internal val SDKComponent.gistProvider: GistProvider get() = singleton { GistSdk( - siteId = moduleConfig.siteId, - dataCenter = moduleConfig.region.code + siteId = inAppModuleConfig.siteId, + dataCenter = inAppModuleConfig.region.code ) } diff --git a/messaginginapp/src/test/java/io/customer/messaginginapp/testutils/core/IntegrationTest.kt b/messaginginapp/src/test/java/io/customer/messaginginapp/testutils/core/IntegrationTest.kt index ebc87189d..79d98763f 100644 --- a/messaginginapp/src/test/java/io/customer/messaginginapp/testutils/core/IntegrationTest.kt +++ b/messaginginapp/src/test/java/io/customer/messaginginapp/testutils/core/IntegrationTest.kt @@ -1,7 +1,6 @@ package io.customer.messaginginapp.testutils.core import io.customer.commontest.config.ApplicationArgument -import io.customer.commontest.config.ClientArgument import io.customer.commontest.config.TestConfig import io.customer.commontest.config.testConfigurationDefault import io.customer.commontest.core.RobolectricTest @@ -9,7 +8,6 @@ import io.customer.commontest.core.RobolectricTest abstract class IntegrationTest : RobolectricTest() { private val defaultTestConfiguration: TestConfig = testConfigurationDefault { argument(ApplicationArgument(applicationMock)) - argument(ClientArgument()) } override fun setup(testConfig: TestConfig) { diff --git a/messaginginapp/src/test/java/io/customer/messaginginapp/testutils/core/JUnitTest.kt b/messaginginapp/src/test/java/io/customer/messaginginapp/testutils/core/JUnitTest.kt index 559cc936a..6c169fd65 100644 --- a/messaginginapp/src/test/java/io/customer/messaginginapp/testutils/core/JUnitTest.kt +++ b/messaginginapp/src/test/java/io/customer/messaginginapp/testutils/core/JUnitTest.kt @@ -1,7 +1,6 @@ package io.customer.messaginginapp.testutils.core import io.customer.commontest.config.ApplicationArgument -import io.customer.commontest.config.ClientArgument import io.customer.commontest.config.TestConfig import io.customer.commontest.config.testConfigurationDefault import io.customer.commontest.core.JUnit5Test @@ -10,7 +9,6 @@ import io.customer.messaginginapp.testutils.mocks.mockAndroidLog abstract class JUnitTest : JUnit5Test() { private val defaultTestConfiguration: TestConfig = testConfigurationDefault { argument(ApplicationArgument(applicationMock)) - argument(ClientArgument()) diGraph { sdk { diff --git a/messagingpush/api/messagingpush.api b/messagingpush/api/messagingpush.api index 32ac3aee0..4495906ff 100644 --- a/messagingpush/api/messagingpush.api +++ b/messagingpush/api/messagingpush.api @@ -115,6 +115,10 @@ public final class io/customer/messagingpush/data/model/CustomerIOParsedPushPayl public synthetic fun newArray (I)[Ljava/lang/Object; } +public final class io/customer/messagingpush/di/DiGraphMessagingPushKt { + public static final fun getPushModuleConfig (Lio/customer/sdk/core/di/SDKComponent;)Lio/customer/messagingpush/MessagingPushModuleConfig; +} + public final class io/customer/messagingpush/processor/PushMessageProcessor$Companion { public static final field RECENT_MESSAGES_MAX_SIZE I public final fun getRecentMessagesQueue ()Ljava/util/concurrent/LinkedBlockingDeque; diff --git a/messagingpush/src/main/java/io/customer/messagingpush/CustomerIOCloudMessagingReceiver.kt b/messagingpush/src/main/java/io/customer/messagingpush/CustomerIOCloudMessagingReceiver.kt index 8199ad7c8..922287b02 100644 --- a/messagingpush/src/main/java/io/customer/messagingpush/CustomerIOCloudMessagingReceiver.kt +++ b/messagingpush/src/main/java/io/customer/messagingpush/CustomerIOCloudMessagingReceiver.kt @@ -7,6 +7,7 @@ import com.google.android.gms.cloudmessaging.CloudMessagingReceiver import io.customer.messagingpush.di.pushMessageProcessor import io.customer.messagingpush.processor.PushMessageProcessor import io.customer.sdk.core.di.SDKComponent +import io.customer.sdk.core.di.setupAndroidComponent /** * Broadcast receiver for listening to push events from GoogleCloudMessaging (GCM). @@ -26,7 +27,7 @@ class CustomerIOCloudMessagingReceiver : BroadcastReceiver() { val extras = intent.extras // Ignore event if no data was received in extras if (extras == null || extras.isEmpty) return - // TODO: Make sure PushMessageProcessor works as expected even if the SDK is not initialized + SDKComponent.setupAndroidComponent(context = context) SDKComponent.pushMessageProcessor.processGCMMessageIntent(intent = intent) } } diff --git a/messagingpush/src/main/java/io/customer/messagingpush/CustomerIOFirebaseMessagingService.kt b/messagingpush/src/main/java/io/customer/messagingpush/CustomerIOFirebaseMessagingService.kt index ea9154185..c819a4bfa 100644 --- a/messagingpush/src/main/java/io/customer/messagingpush/CustomerIOFirebaseMessagingService.kt +++ b/messagingpush/src/main/java/io/customer/messagingpush/CustomerIOFirebaseMessagingService.kt @@ -6,6 +6,7 @@ import com.google.firebase.messaging.RemoteMessage import io.customer.messagingpush.di.pushMessageProcessor import io.customer.sdk.communication.Event import io.customer.sdk.core.di.SDKComponent +import io.customer.sdk.core.di.setupAndroidComponent open class CustomerIOFirebaseMessagingService : FirebaseMessagingService() { @@ -47,6 +48,7 @@ open class CustomerIOFirebaseMessagingService : FirebaseMessagingService() { } private fun handleNewToken(context: Context, token: String) { + SDKComponent.setupAndroidComponent(context = context) eventBus.publish( Event.RegisterDeviceTokenEvent(token) ) @@ -57,7 +59,7 @@ open class CustomerIOFirebaseMessagingService : FirebaseMessagingService() { remoteMessage: RemoteMessage, handleNotificationTrigger: Boolean = true ): Boolean { - // TODO: Make sure PushNotificationHandler works as expected even if the SDK is not initialized + SDKComponent.setupAndroidComponent(context = context) val handler = CustomerIOPushNotificationHandler( pushMessageProcessor = SDKComponent.pushMessageProcessor, remoteMessage = remoteMessage diff --git a/messagingpush/src/main/java/io/customer/messagingpush/CustomerIOPushNotificationHandler.kt b/messagingpush/src/main/java/io/customer/messagingpush/CustomerIOPushNotificationHandler.kt index 3e59c7a57..ae4677e1a 100644 --- a/messagingpush/src/main/java/io/customer/messagingpush/CustomerIOPushNotificationHandler.kt +++ b/messagingpush/src/main/java/io/customer/messagingpush/CustomerIOPushNotificationHandler.kt @@ -5,7 +5,6 @@ import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.content.Intent -import android.content.pm.PackageManager import android.graphics.BitmapFactory import android.media.RingtoneManager import android.os.Build @@ -18,12 +17,13 @@ import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage import io.customer.messagingpush.activity.NotificationClickReceiverActivity import io.customer.messagingpush.data.model.CustomerIOParsedPushPayload -import io.customer.messagingpush.di.moduleConfig +import io.customer.messagingpush.di.pushModuleConfig import io.customer.messagingpush.extensions.* import io.customer.messagingpush.processor.PushMessageProcessor import io.customer.messagingpush.util.PushTrackingUtil.Companion.DELIVERY_ID_KEY import io.customer.messagingpush.util.PushTrackingUtil.Companion.DELIVERY_TOKEN_KEY import io.customer.sdk.core.di.SDKComponent +import io.customer.sdk.core.extensions.applicationMetaData import java.net.URL import kotlin.math.abs import kotlinx.coroutines.Dispatchers @@ -57,7 +57,7 @@ internal class CustomerIOPushNotificationHandler( private val logger = SDKComponent.logger private val moduleConfig: MessagingPushModuleConfig - get() = diGraph.moduleConfig + get() = diGraph.pushModuleConfig private val bundle: Bundle by lazy { Bundle().apply { @@ -114,16 +114,7 @@ internal class CustomerIOPushNotificationHandler( bundle.putInt(NOTIFICATION_REQUEST_CODE, requestCode) - val applicationInfo = try { - context.packageManager.getApplicationInfo( - context.packageName, - PackageManager.GET_META_DATA - ) - } catch (ex: Exception) { - logger.error("Package not found ${ex.message}") - null - } - val appMetaData = applicationInfo?.metaData + val appMetaData = context.applicationMetaData() @DrawableRes val smallIcon: Int = diff --git a/messagingpush/src/main/java/io/customer/messagingpush/activity/NotificationClickReceiverActivity.kt b/messagingpush/src/main/java/io/customer/messagingpush/activity/NotificationClickReceiverActivity.kt index c40e781ff..377996979 100644 --- a/messagingpush/src/main/java/io/customer/messagingpush/activity/NotificationClickReceiverActivity.kt +++ b/messagingpush/src/main/java/io/customer/messagingpush/activity/NotificationClickReceiverActivity.kt @@ -5,6 +5,7 @@ import android.content.Intent import android.os.Bundle import io.customer.messagingpush.di.pushMessageProcessor import io.customer.sdk.core.di.SDKComponent +import io.customer.sdk.core.di.setupAndroidComponent import io.customer.sdk.tracking.TrackableScreen /** @@ -24,6 +25,7 @@ class NotificationClickReceiverActivity : Activity(), TrackableScreen { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + SDKComponent.setupAndroidComponent(context = this) handleIntent(data = intent) } diff --git a/messagingpush/src/main/java/io/customer/messagingpush/di/DiGraphMessagingPush.kt b/messagingpush/src/main/java/io/customer/messagingpush/di/DiGraphMessagingPush.kt index 6268d080c..6f202b4f1 100644 --- a/messagingpush/src/main/java/io/customer/messagingpush/di/DiGraphMessagingPush.kt +++ b/messagingpush/src/main/java/io/customer/messagingpush/di/DiGraphMessagingPush.kt @@ -25,13 +25,13 @@ The use of extensions was chosen over creating a separate graph class for each m internal val AndroidSDKComponent.fcmTokenProvider: DeviceTokenProvider get() = newInstance { FCMTokenProviderImpl(context = applicationContext) } -internal val SDKComponent.moduleConfig: MessagingPushModuleConfig +val SDKComponent.pushModuleConfig: MessagingPushModuleConfig get() = newInstance { modules[ModuleMessagingPushFCM.MODULE_NAME]?.moduleConfig as? MessagingPushModuleConfig ?: MessagingPushModuleConfig.default() } internal val SDKComponent.deepLinkUtil: DeepLinkUtil - get() = newInstance { DeepLinkUtilImpl(logger, moduleConfig) } + get() = newInstance { DeepLinkUtilImpl(logger, pushModuleConfig) } @InternalCustomerIOApi val SDKComponent.pushTrackingUtil: PushTrackingUtil @@ -41,7 +41,7 @@ internal val SDKComponent.pushMessageProcessor: PushMessageProcessor get() = singleton { PushMessageProcessorImpl( logger = logger, - moduleConfig = moduleConfig, + moduleConfig = pushModuleConfig, deepLinkUtil = deepLinkUtil ) } diff --git a/messagingpush/src/test/java/io/customer/messagingpush/testutils/core/IntegrationTest.kt b/messagingpush/src/test/java/io/customer/messagingpush/testutils/core/IntegrationTest.kt index c20c55377..89117ad34 100644 --- a/messagingpush/src/test/java/io/customer/messagingpush/testutils/core/IntegrationTest.kt +++ b/messagingpush/src/test/java/io/customer/messagingpush/testutils/core/IntegrationTest.kt @@ -1,7 +1,6 @@ package io.customer.messagingpush.testutils.core import io.customer.commontest.config.ApplicationArgument -import io.customer.commontest.config.ClientArgument import io.customer.commontest.config.TestConfig import io.customer.commontest.config.testConfigurationDefault import io.customer.commontest.core.RobolectricTest @@ -9,7 +8,6 @@ import io.customer.commontest.core.RobolectricTest abstract class IntegrationTest : RobolectricTest() { private val defaultTestConfiguration: TestConfig = testConfigurationDefault { argument(ApplicationArgument(applicationMock)) - argument(ClientArgument()) } override fun setup(testConfig: TestConfig) { diff --git a/messagingpush/src/test/java/io/customer/messagingpush/testutils/core/JUnitTest.kt b/messagingpush/src/test/java/io/customer/messagingpush/testutils/core/JUnitTest.kt index b2379f4e9..5a162f922 100644 --- a/messagingpush/src/test/java/io/customer/messagingpush/testutils/core/JUnitTest.kt +++ b/messagingpush/src/test/java/io/customer/messagingpush/testutils/core/JUnitTest.kt @@ -1,7 +1,6 @@ package io.customer.messagingpush.testutils.core import io.customer.commontest.config.ApplicationArgument -import io.customer.commontest.config.ClientArgument import io.customer.commontest.config.TestConfig import io.customer.commontest.config.testConfigurationDefault import io.customer.commontest.core.JUnit5Test @@ -9,7 +8,6 @@ import io.customer.commontest.core.JUnit5Test abstract class JUnitTest : JUnit5Test() { private val defaultTestConfiguration: TestConfig = testConfigurationDefault { argument(ApplicationArgument(applicationMock)) - argument(ClientArgument()) } override fun setup(testConfig: TestConfig) { diff --git a/tracking-migration/src/test/java/io/customer/tracking/migration/testutils/core/IntegrationTest.kt b/tracking-migration/src/test/java/io/customer/tracking/migration/testutils/core/IntegrationTest.kt index 57c166937..e84734e30 100644 --- a/tracking-migration/src/test/java/io/customer/tracking/migration/testutils/core/IntegrationTest.kt +++ b/tracking-migration/src/test/java/io/customer/tracking/migration/testutils/core/IntegrationTest.kt @@ -1,7 +1,6 @@ package io.customer.tracking.migration.testutils.core import io.customer.commontest.config.ApplicationArgument -import io.customer.commontest.config.ClientArgument import io.customer.commontest.config.TestConfig import io.customer.commontest.core.RobolectricTest import io.customer.sdk.core.di.SDKComponent @@ -11,7 +10,6 @@ import io.customer.tracking.migration.testutils.extensions.migrationSDKComponent abstract class IntegrationTest : RobolectricTest() { private val defaultTestConfiguration: TrackingMigrationTestConfig = testConfiguration { argument(ApplicationArgument(applicationMock)) - argument(ClientArgument()) } override fun setup(testConfig: TestConfig) { diff --git a/tracking-migration/src/test/java/io/customer/tracking/migration/testutils/core/JUnitTest.kt b/tracking-migration/src/test/java/io/customer/tracking/migration/testutils/core/JUnitTest.kt index 0bf64a05f..d40a02cbb 100644 --- a/tracking-migration/src/test/java/io/customer/tracking/migration/testutils/core/JUnitTest.kt +++ b/tracking-migration/src/test/java/io/customer/tracking/migration/testutils/core/JUnitTest.kt @@ -1,7 +1,6 @@ package io.customer.tracking.migration.testutils.core import io.customer.commontest.config.ApplicationArgument -import io.customer.commontest.config.ClientArgument import io.customer.commontest.config.TestConfig import io.customer.commontest.core.JUnit5Test import io.customer.sdk.core.di.SDKComponent @@ -11,7 +10,6 @@ import io.customer.tracking.migration.testutils.extensions.migrationSDKComponent abstract class JUnitTest : JUnit5Test() { private val defaultTestConfiguration: TrackingMigrationTestConfig = testConfiguration { argument(ApplicationArgument(applicationMock)) - argument(ClientArgument()) } override fun setup(testConfig: TestConfig) {