diff --git a/embrace-android-sdk/config/detekt/baseline.xml b/embrace-android-sdk/config/detekt/baseline.xml index 90989dc14c..bbf00e21fc 100644 --- a/embrace-android-sdk/config/detekt/baseline.xml +++ b/embrace-android-sdk/config/detekt/baseline.xml @@ -6,7 +6,6 @@ DataClassContainsFunctions:LegacyExceptionError.kt$LegacyExceptionError$private fun getExceptionInfo(ex: Throwable?): List<LegacyExceptionInfo> DataClassShouldBeImmutable:LegacyExceptionError.kt$LegacyExceptionError$@Json(name = "c") var occurrences: Int = 0 DataClassShouldBeImmutable:LegacyExceptionError.kt$LegacyExceptionError$@Json(name = "rep") var exceptionErrors: MutableList<LegacyExceptionErrorInfo> = mutableListOf() - LargeClass:EmbraceImpl.kt$EmbraceImpl - TooManyFunctions:EmbraceImpl.kt$EmbraceImpl + TooManyFunctions:EmbraceImpl.kt$EmbraceImpl : UserApi diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/EmbraceImpl.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/EmbraceImpl.kt index aca0971878..007c4dc3c1 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/EmbraceImpl.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/EmbraceImpl.kt @@ -18,6 +18,8 @@ import io.embrace.android.embracesdk.internal.EmbraceInternalInterface import io.embrace.android.embracesdk.internal.IdGenerator.Companion.generateW3CTraceparent import io.embrace.android.embracesdk.internal.Systrace.endSynchronous import io.embrace.android.embracesdk.internal.Systrace.startSynchronous +import io.embrace.android.embracesdk.internal.api.delegate.SdkCallChecker +import io.embrace.android.embracesdk.internal.api.delegate.UserApiDelegate import io.embrace.android.embracesdk.internal.spans.EmbraceTracer import io.embrace.android.embracesdk.internal.utils.getSafeStackTrace import io.embrace.android.embracesdk.logging.EmbLogger @@ -29,7 +31,6 @@ import io.embrace.android.embracesdk.worker.WorkerName import io.embrace.android.embracesdk.worker.WorkerThreadModule import io.opentelemetry.sdk.logs.export.LogRecordExporter import io.opentelemetry.sdk.trace.export.SpanExporter -import java.util.concurrent.atomic.AtomicBoolean import java.util.regex.Pattern /** @@ -41,8 +42,11 @@ import java.util.regex.Pattern */ @SuppressLint("EmbracePublicApiPackageRule") internal class EmbraceImpl @JvmOverloads constructor( - private val bootstrapper: ModuleInitBootstrapper = ModuleInitBootstrapper() -) { + private val bootstrapper: ModuleInitBootstrapper = ModuleInitBootstrapper(), + private val sdkCallChecker: SdkCallChecker = + SdkCallChecker(bootstrapper.initModule.logger, bootstrapper.initModule.telemetryService), + private val userApiDelegate: UserApiDelegate = UserApiDelegate(bootstrapper, sdkCallChecker) +) : UserApi by userApiDelegate { @JvmField val tracer: EmbraceTracer = bootstrapper.openTelemetryModule.embraceTracer @@ -51,10 +55,6 @@ internal class EmbraceImpl @JvmOverloads constructor( UninitializedSdkInternalInterfaceImpl(bootstrapper.openTelemetryModule.internalTracer) } - /** - * Whether the Embrace SDK has been started yet. - */ - private val started = AtomicBoolean(false) private val sdkClock = bootstrapper.initModule.clock private val logger = bootstrapper.initModule.logger @@ -215,7 +215,7 @@ internal class EmbraceImpl @JvmOverloads constructor( logger.logInfo(startMsg, null) val endTimeMs = sdkClock.now() - started.set(true) + sdkCallChecker.started.set(true) endSynchronous() val inForeground = !essentialServiceModule.processStateService.isInBackground @@ -279,7 +279,7 @@ internal class EmbraceImpl @JvmOverloads constructor( * * @return true if the SDK is started, false otherwise */ - fun isStarted(): Boolean = started.get() + fun isStarted(): Boolean = sdkCallChecker.started.get() /** * Sets a custom app ID that overrides the one specified at build time. Must be called before @@ -313,7 +313,7 @@ internal class EmbraceImpl @JvmOverloads constructor( * Shuts down the Embrace SDK. */ fun stop() { - if (started.compareAndSet(true, false)) { + if (sdkCallChecker.started.compareAndSet(true, false)) { logger.logInfo("Shutting down Embrace SDK.", null) try { application?.let { @@ -329,118 +329,11 @@ internal class EmbraceImpl @JvmOverloads constructor( } } - /** - * Sets the user ID. This would typically be some form of unique identifier such as a UUID or - * database key for the user. - * - * @param userId the unique identifier for the user - */ - fun setUserIdentifier(userId: String?) { - if (checkSdkStartedAndLogPublicApiUsage("set_user_identifier")) { - userService?.setUserIdentifier(userId) - // Update user info in NDK service - ndkService?.onUserInfoUpdate() - } - } - - /** - * Clears the currently set user ID. For example, if the user logs out. - */ - fun clearUserIdentifier() { - if (checkSdkStartedAndLogPublicApiUsage("clear_user_identifier")) { - userService?.clearUserIdentifier() - } - } - - /** - * Sets the current user's email address. - * - * @param email the email address of the current user - */ - fun setUserEmail(email: String?) { - if (checkSdkStartedAndLogPublicApiUsage("set_user_email")) { - userService?.setUserEmail(email) - // Update user info in NDK service - ndkService?.onUserInfoUpdate() - } - } - - /** - * Clears the currently set user's email address. - */ - fun clearUserEmail() { - if (checkSdkStartedAndLogPublicApiUsage("clear_user_email")) { - userService?.clearUserEmail() - // Update user info in NDK service - ndkService?.onUserInfoUpdate() - } - } - - /** - * Sets this user as a paying user. This adds a persona to the user's identity. - */ - fun setUserAsPayer() { - if (checkSdkStartedAndLogPublicApiUsage("set_user_as_payer")) { - userService?.setUserAsPayer() - // Update user info in NDK service - ndkService?.onUserInfoUpdate() - } - } - - /** - * Clears this user as a paying user. This would typically be called if a user is no longer - * paying for the service and has reverted back to a basic user. - */ - fun clearUserAsPayer() { - if (checkSdkStartedAndLogPublicApiUsage("clear_user_as_payer")) { - userService?.clearUserAsPayer() - // Update user info in NDK service - ndkService?.onUserInfoUpdate() - } - } - - /** - * Sets a custom user persona. A persona is a trait associated with a given user. - * - * @param persona the persona to set - */ - fun addUserPersona(persona: String) { - if (checkSdkStartedAndLogPublicApiUsage("add_user_persona")) { - userService?.addUserPersona(persona) - // Update user info in NDK service - ndkService?.onUserInfoUpdate() - } - } - - /** - * Clears the custom user persona, if it is set. - * - * @param persona the persona to clear - */ - fun clearUserPersona(persona: String) { - if (checkSdkStartedAndLogPublicApiUsage("clear_user_persona")) { - userService?.clearUserPersona(persona) - // Update user info in NDK service - ndkService?.onUserInfoUpdate() - } - } - - /** - * Clears all custom user personas from the user. - */ - fun clearAllUserPersonas() { - if (checkSdkStartedAndLogPublicApiUsage("clear_user_personas")) { - userService?.clearAllUserPersonas() - // Update user info in NDK service - ndkService?.onUserInfoUpdate() - } - } - /** * Adds a property to the current session. */ fun addSessionProperty(key: String, value: String, permanent: Boolean): Boolean { - if (checkSdkStartedAndLogPublicApiUsage("add_session_property")) { + if (sdkCallChecker.check("add_session_property")) { return sessionPropertiesService?.addProperty(key, value, permanent) ?: false } return false @@ -450,43 +343,19 @@ internal class EmbraceImpl @JvmOverloads constructor( * Removes a property from the current session. */ fun removeSessionProperty(key: String): Boolean { - if (checkSdkStartedAndLogPublicApiUsage("remove_session_property")) { + if (sdkCallChecker.check("remove_session_property")) { return sessionPropertiesService?.removeProperty(key) ?: false } return false } fun getSessionProperties(): Map? { - if (checkSdkStartedAndLogPublicApiUsage("get_session_properties")) { + if (sdkCallChecker.check("get_session_properties")) { return sessionPropertiesService?.getProperties() } return null } - /** - * Sets the username of the currently logged in user. - * - * @param username the username to set - */ - fun setUsername(username: String?) { - if (checkSdkStartedAndLogPublicApiUsage("set_username")) { - userService?.setUsername(username) - // Update user info in NDK service - ndkService?.onUserInfoUpdate() - } - } - - /** - * Clears the username of the currently logged in user, for example if the user has logged out. - */ - fun clearUsername() { - if (checkSdkStartedAndLogPublicApiUsage("clear_username")) { - userService?.clearUsername() - // Update user info in NDK service - ndkService?.onUserInfoUpdate() - } - } - /** * Starts a 'moment'. Moments are used for encapsulating particular activities within * the app, such as a user adding an item to their shopping cart. @@ -499,7 +368,7 @@ internal class EmbraceImpl @JvmOverloads constructor( * @param properties custom key-value pairs to provide with the moment */ fun startMoment(name: String, identifier: String?, properties: Map?) { - if (checkSdkStartedAndLogPublicApiUsage("start_moment")) { + if (sdkCallChecker.check("start_moment")) { eventService?.startEvent(name, identifier, normalizeProperties(properties, logger)) onActivityReported() } @@ -516,7 +385,7 @@ internal class EmbraceImpl @JvmOverloads constructor( * @param properties custom key-value pairs to provide with the moment */ fun endMoment(name: String, identifier: String?, properties: Map?) { - if (checkSdkStartedAndLogPublicApiUsage("end_moment")) { + if (sdkCallChecker.check("end_moment")) { eventService?.endEvent(name, identifier, normalizeProperties(properties, logger)) onActivityReported() } @@ -532,7 +401,17 @@ internal class EmbraceImpl @JvmOverloads constructor( } fun getTraceIdHeader(): String { - if (configService != null && checkSdkStarted("get_trace_id_header", false)) { + if (configService != null && run { + val isStarted = isStarted() + if (!isStarted) { + logger.logSdkNotInitialized("get_trace_id_header") + } + if (telemetryService != null && false) { + telemetryService?.onPublicApiCalled("get_trace_id_header") + } + isStarted + } + ) { return configService?.networkBehavior?.getTraceIdHeader() ?: NetworkBehavior.CONFIG_TRACE_ID_HEADER_DEFAULT_VALUE } @@ -542,7 +421,7 @@ internal class EmbraceImpl @JvmOverloads constructor( fun generateW3cTraceparent(): String = generateW3CTraceparent() fun recordNetworkRequest(request: EmbraceNetworkRequest) { - if (checkSdkStartedAndLogPublicApiUsage("record_network_request")) { + if (sdkCallChecker.check("record_network_request")) { logNetworkRequest(request) } } @@ -620,7 +499,7 @@ internal class EmbraceImpl @JvmOverloads constructor( exceptionName: String? = null, exceptionMessage: String? = null ) { - if (checkSdkStartedAndLogPublicApiUsage("log_message")) { + if (sdkCallChecker.check("log_message")) { try { appFramework?.let { logMessageService?.log( @@ -653,7 +532,7 @@ internal class EmbraceImpl @JvmOverloads constructor( * @param message the name of the breadcrumb to log */ fun addBreadcrumb(message: String) { - if (checkSdkStartedAndLogPublicApiUsage("add_breadcrumb")) { + if (sdkCallChecker.check("add_breadcrumb")) { breadcrumbService?.logCustom(message, sdkClock.now()) onActivityReported() } @@ -663,7 +542,7 @@ internal class EmbraceImpl @JvmOverloads constructor( * Logs an internal error to the Embrace SDK - this is not intended for public use. */ fun logInternalError(message: String?, details: String?) { - if (checkSdkStartedAndLogPublicApiUsage("log_internal_error")) { + if (sdkCallChecker.check("log_internal_error")) { if (message == null) { return } @@ -681,7 +560,7 @@ internal class EmbraceImpl @JvmOverloads constructor( * Logs an internal error to the Embrace SDK - this is not intended for public use. */ fun logInternalError(error: Throwable) { - if (checkSdkStartedAndLogPublicApiUsage("log_internal_error")) { + if (sdkCallChecker.check("log_internal_error")) { internalErrorService?.handleInternalError(error) } } @@ -693,13 +572,13 @@ internal class EmbraceImpl @JvmOverloads constructor( * Cleans all the user info on the device. */ fun endSession(clearUserInfo: Boolean) { - if (checkSdkStartedAndLogPublicApiUsage("end_session")) { + if (sdkCallChecker.check("end_session")) { sessionOrchestrator?.endSessionWithManual(clearUserInfo) } } fun getDeviceId(): String = when { - checkSdkStartedAndLogPublicApiUsage("get_device_id") -> + sdkCallChecker.check("get_device_id") -> preferencesService?.deviceIdentifier ?: "" @@ -715,7 +594,7 @@ internal class EmbraceImpl @JvmOverloads constructor( * @param name the name of the fragment to log */ fun startView(name: String): Boolean { - if (checkSdkStartedAndLogPublicApiUsage("start_view")) { + if (sdkCallChecker.check("start_view")) { return breadcrumbService?.startView(name) ?: false } return false @@ -730,7 +609,7 @@ internal class EmbraceImpl @JvmOverloads constructor( * @param name the name of the fragment to log */ fun endView(name: String): Boolean { - if (checkSdkStartedAndLogPublicApiUsage("end_view")) { + if (sdkCallChecker.check("end_view")) { return breadcrumbService?.endView(name) ?: false } return false @@ -755,7 +634,7 @@ internal class EmbraceImpl @JvmOverloads constructor( messageDeliveredPriority: Int, type: PushNotificationBreadcrumb.NotificationType ) { - if (checkSdkStartedAndLogPublicApiUsage("log_push_notification")) { + if (sdkCallChecker.check("log_push_notification")) { pushNotificationService?.logPushNotification(title, body, topic, id, notificationPriority, messageDeliveredPriority, type) onActivityReported() } @@ -767,7 +646,7 @@ internal class EmbraceImpl @JvmOverloads constructor( * @param url the url to log */ fun logWebView(url: String?) { - if (checkSdkStartedAndLogPublicApiUsage("log_web_view")) { + if (sdkCallChecker.check("log_web_view")) { breadcrumbService?.logWebView(url, sdkClock.now()) onActivityReported() } @@ -781,7 +660,7 @@ internal class EmbraceImpl @JvmOverloads constructor( * @param type the type of tap that occurred */ fun logTap(point: Pair, elementName: String, type: TapBreadcrumbType) { - if (checkSdkStartedAndLogPublicApiUsage("log_tap")) { + if (sdkCallChecker.check("log_tap")) { breadcrumbService?.logTap(point, elementName, sdkClock.now(), type) onActivityReported() } @@ -795,7 +674,17 @@ internal class EmbraceImpl @JvmOverloads constructor( fun getCurrentSessionId(): String? { val localSessionIdTracker = sessionIdTracker - if (localSessionIdTracker != null && checkSdkStarted("get_current_session_id", false)) { + if (localSessionIdTracker != null && run { + val isStarted = isStarted() + if (!isStarted) { + logger.logSdkNotInitialized("get_current_session_id") + } + if (telemetryService != null && false) { + telemetryService?.onPublicApiCalled("get_current_session_id") + } + isStarted + } + ) { val sessionId = localSessionIdTracker.getActiveSessionId() if (sessionId != null) { return sessionId @@ -855,7 +744,7 @@ internal class EmbraceImpl @JvmOverloads constructor( bytesSent: Int, output: String ) { - if (checkSdkStartedAndLogPublicApiUsage("log_react_native_action")) { + if (sdkCallChecker.check("log_react_native_action")) { breadcrumbService?.logRnAction(name, startTime, endTime, properties, bytesSent, output) } } @@ -875,7 +764,7 @@ internal class EmbraceImpl @JvmOverloads constructor( return } - if (checkSdkStartedAndLogPublicApiUsage("log RN view")) { + if (sdkCallChecker.check("log RN view")) { breadcrumbService?.logView(screen, sdkClock.now()) onActivityReported() } @@ -884,7 +773,7 @@ internal class EmbraceImpl @JvmOverloads constructor( fun getUnityInternalInterface(): UnityInternalInterface? = internalInterfaceModule?.unityInternalInterface fun installUnityThreadSampler() { - if (checkSdkStartedAndLogPublicApiUsage("install_unity_thread_sampler")) { + if (sdkCallChecker.check("install_unity_thread_sampler")) { sampleCurrentThreadDuringAnrs() } } @@ -927,28 +816,6 @@ internal class EmbraceImpl @JvmOverloads constructor( } } - /** - * Checks if the SDK is started and logs the public API usage. - * - * - * Every public API usage should go through this method, except the ones that are called too often and may cause a performance hit. - * For instance, get_current_session_id and get_trace_id_header go directly through checkSdkStarted. - */ - private fun checkSdkStartedAndLogPublicApiUsage(action: String): Boolean { - return checkSdkStarted(action, true) - } - - private fun checkSdkStarted(action: String, logPublicApiUsage: Boolean): Boolean { - val isStarted = isStarted() - if (!isStarted) { - logger.logSdkNotInitialized(action) - } - if (telemetryService != null && logPublicApiUsage) { - telemetryService?.onPublicApiCalled(action) - } - return isStarted - } - fun addSpanExporter(spanExporter: SpanExporter) { if (isStarted()) { logger.logError("A SpanExporter can only be added before the SDK is started.", null) diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/injection/InjectEmbraceImpl.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/injection/InjectEmbraceImpl.kt index bd4f0a3170..6d3152269f 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/injection/InjectEmbraceImpl.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/injection/InjectEmbraceImpl.kt @@ -1,6 +1,7 @@ package io.embrace.android.embracesdk.injection import io.embrace.android.embracesdk.EmbraceImpl +import io.embrace.android.embracesdk.internal.api.delegate.SdkCallChecker import io.embrace.android.embracesdk.internal.utils.Provider import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty @@ -15,6 +16,11 @@ internal inline fun EmbraceImpl.embraceImplInject( noinline provider: Provider ): ReadOnlyProperty = EmbraceImplFieldDelegate(::isStarted, provider) +internal inline fun implInject( + checker: SdkCallChecker, + noinline provider: Provider +): ReadOnlyProperty = EmbraceImplFieldDelegate({ checker.started.get() }, provider) + internal class EmbraceImplFieldDelegate( private val startedCheck: () -> Boolean, provider: Provider diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/api/delegate/SdkCallChecker.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/api/delegate/SdkCallChecker.kt new file mode 100644 index 0000000000..d96041e9f2 --- /dev/null +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/api/delegate/SdkCallChecker.kt @@ -0,0 +1,31 @@ +package io.embrace.android.embracesdk.internal.api.delegate + +import io.embrace.android.embracesdk.logging.EmbLogger +import io.embrace.android.embracesdk.telemetry.TelemetryService +import java.util.concurrent.atomic.AtomicBoolean + +internal class SdkCallChecker( + private val logger: EmbLogger, + private val telemetryService: TelemetryService? +) { + + /** + * Whether the Embrace SDK has been started yet. + */ + val started = AtomicBoolean(false) + + /** + * Checks if the SDK is started and logs the public API usage. + * + * Every public API usage should go through this method, except the ones that are called too often and may cause a performance hit. + * For instance, get_current_session_id and get_trace_id_header go directly through checkSdkStarted. + */ + fun check(action: String): Boolean { + val isStarted = started.get() + if (!isStarted) { + logger.logSdkNotInitialized(action) + } + telemetryService?.onPublicApiCalled(action) + return isStarted + } +} diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/api/delegate/UserApiDelegate.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/api/delegate/UserApiDelegate.kt new file mode 100644 index 0000000000..c44763b2dd --- /dev/null +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/api/delegate/UserApiDelegate.kt @@ -0,0 +1,145 @@ +package io.embrace.android.embracesdk.internal.api.delegate + +import io.embrace.android.embracesdk.UserApi +import io.embrace.android.embracesdk.injection.ModuleInitBootstrapper +import io.embrace.android.embracesdk.injection.implInject + +internal class UserApiDelegate( + bootstrapper: ModuleInitBootstrapper, + private val sdkCallChecker: SdkCallChecker +) : UserApi { + + private val userService by implInject(sdkCallChecker) { bootstrapper.essentialServiceModule.userService } + private val ndkService by implInject(sdkCallChecker) { bootstrapper.nativeModule.ndkService } + + /** + * Sets the user ID. This would typically be some form of unique identifier such as a UUID or + * database key for the user. + * + * @param userId the unique identifier for the user + */ + override fun setUserIdentifier(userId: String?) { + if (sdkCallChecker.check("set_user_identifier")) { + userService?.setUserIdentifier(userId) + // Update user info in NDK service + ndkService?.onUserInfoUpdate() + } + } + + /** + * Clears the currently set user ID. For example, if the user logs out. + */ + override fun clearUserIdentifier() { + if (sdkCallChecker.check("clear_user_identifier")) { + userService?.clearUserIdentifier() + } + } + + /** + * Sets the current user's email address. + * + * @param email the email address of the current user + */ + override fun setUserEmail(email: String?) { + if (sdkCallChecker.check("set_user_email")) { + userService?.setUserEmail(email) + // Update user info in NDK service + ndkService?.onUserInfoUpdate() + } + } + + /** + * Clears the currently set user's email address. + */ + override fun clearUserEmail() { + if (sdkCallChecker.check("clear_user_email")) { + userService?.clearUserEmail() + // Update user info in NDK service + ndkService?.onUserInfoUpdate() + } + } + + /** + * Sets this user as a paying user. This adds a persona to the user's identity. + */ + override fun setUserAsPayer() { + if (sdkCallChecker.check("set_user_as_payer")) { + userService?.setUserAsPayer() + // Update user info in NDK service + ndkService?.onUserInfoUpdate() + } + } + + /** + * Clears this user as a paying user. This would typically be called if a user is no longer + * paying for the service and has reverted back to a basic user. + */ + override fun clearUserAsPayer() { + if (sdkCallChecker.check("clear_user_as_payer")) { + userService?.clearUserAsPayer() + // Update user info in NDK service + ndkService?.onUserInfoUpdate() + } + } + + /** + * Sets a custom user persona. A persona is a trait associated with a given user. + * + * @param persona the persona to set + */ + override fun addUserPersona(persona: String) { + if (sdkCallChecker.check("add_user_persona")) { + userService?.addUserPersona(persona) + // Update user info in NDK service + ndkService?.onUserInfoUpdate() + } + } + + /** + * Clears the custom user persona, if it is set. + * + * @param persona the persona to clear + */ + override fun clearUserPersona(persona: String) { + if (sdkCallChecker.check("clear_user_persona")) { + userService?.clearUserPersona(persona) + // Update user info in NDK service + ndkService?.onUserInfoUpdate() + } + } + + /** + * Clears all custom user personas from the user. + */ + override fun clearAllUserPersonas() { + if (sdkCallChecker.check("clear_user_personas")) { + userService?.clearAllUserPersonas() + // Update user info in NDK service + ndkService?.onUserInfoUpdate() + } + } + + /** + * Sets the username of the currently logged in user. + * + * @param username the username to set + */ + override fun setUsername(username: String?) { + if (sdkCallChecker.check("set_username")) { + userService?.setUsername(username) + // Update user info in NDK service + ndkService?.onUserInfoUpdate() + } + } + + /** + * Clears the username of the currently logged in user, for example if the user has logged out. + */ + override fun clearUsername() { + if (sdkCallChecker.check("clear_username")) { + userService?.clearUsername() + // Update user info in NDK service + ndkService?.onUserInfoUpdate() + } + } +}