diff --git a/OneSignalSDK/build.gradle b/OneSignalSDK/build.gradle
index c02338492e..eab205d258 100644
--- a/OneSignalSDK/build.gradle
+++ b/OneSignalSDK/build.gradle
@@ -14,8 +14,8 @@ buildscript {
huaweiAgconnectVersion = '1.9.1.304'
huaweiHMSPushVersion = '6.3.0.304'
huaweiHMSLocationVersion = '4.0.0.300'
- kotlinVersion = '2.2.0'
- dokkaVersion = '1.9.10'
+ kotlinVersion = '1.9.25'
+ dokkaVersion = '1.9.10' // Dokka version compatible with Kotlin 1.9.25
coroutinesVersion = '1.7.3'
kotestVersion = '5.8.0'
ioMockVersion = '1.13.2'
@@ -25,10 +25,6 @@ buildscript {
ktlintVersion = '0.50.0' // Used by Spotless for Kotlin formatting (compatible with Kotlin 1.7.10)
spotlessVersion = '6.25.0'
tdunningJsonForTest = '1.0' // DO NOT upgrade for tests, using an old version so it matches AOSP
- // OpenTelemetry versions
- opentelemetryBomVersion = '1.55.0'
- opentelemetrySemconvVersion = '1.37.0'
- opentelemetryDiskBufferingVersion = '1.51.0-alpha'
sharedRepos = {
google()
@@ -49,9 +45,11 @@ buildscript {
]
}
- repositories sharedRepos
- dependencies {
- classpath sharedDeps
+ buildscript {
+ repositories sharedRepos
+ dependencies {
+ classpath sharedDeps
+ }
}
}
diff --git a/OneSignalSDK/detekt/detekt-baseline-core.xml b/OneSignalSDK/detekt/detekt-baseline-core.xml
index 797b08f41e..20c78da602 100644
--- a/OneSignalSDK/detekt/detekt-baseline-core.xml
+++ b/OneSignalSDK/detekt/detekt-baseline-core.xml
@@ -6,11 +6,10 @@
ComplexCondition:TrackGooglePurchase.kt$TrackGooglePurchase.Companion$args.size == 4 && args[0] == Int::class.javaPrimitiveType && args[1] == String::class.java && args[2] == String::class.java && args[3] == Bundle::class.java && returnType == Bundle::class.java
ComplexCondition:TrackGooglePurchase.kt$TrackGooglePurchase.Companion$args.size == 4 && args[0] == Int::class.javaPrimitiveType && args[1] == String::class.java && args[2] == String::class.java && args[3] == String::class.java
ComplexMethod:ConfigModelStoreListener.kt$ConfigModelStoreListener$private fun fetchParams()
- ComplexMethod:HttpClient.kt$HttpClient$@OptIn(DelicateCoroutinesApi::class) private suspend fun makeRequestIODispatcher( url: String, method: String?, jsonBody: JSONObject?, timeout: Int, headers: OptionalHeaders?, ): HttpResponse
+ ComplexMethod:HttpClient.kt$HttpClient$private suspend fun makeRequestIODispatcher( url: String, method: String?, jsonBody: JSONObject?, timeout: Int, headers: OptionalHeaders?, ): HttpResponse
ComplexMethod:IdentityOperationExecutor.kt$IdentityOperationExecutor$override suspend fun execute(operations: List<Operation>): ExecutionResponse
ComplexMethod:LoginUserOperationExecutor.kt$LoginUserOperationExecutor$private suspend fun createUser( createUserOperation: LoginUserOperation, operations: List<Operation>, ): ExecutionResponse
ComplexMethod:OSDatabase.kt$OSDatabase$@Synchronized private fun internalOnUpgrade( db: SQLiteDatabase, oldVersion: Int, newVersion: Int, )
- ComplexMethod:OneSignalImp.kt$OneSignalImp$override fun initWithContext( context: Context, appId: String?, ): Boolean
ComplexMethod:OperationModelStore.kt$OperationModelStore$override fun create(jsonObject: JSONObject?): Operation?
ComplexMethod:OperationRepo.kt$OperationRepo$internal suspend fun executeOperations(ops: List<OperationQueueItem>)
ComplexMethod:PreferencesService.kt$PreferencesService$private fun get( store: String, key: String, type: Class<*>, defValue: Any?, ): Any?
@@ -139,7 +138,6 @@
ConstructorParameterNaming:UpdateUserOperationExecutor.kt$UpdateUserOperationExecutor$private val _propertiesModelStore: PropertiesModelStore
ConstructorParameterNaming:UpdateUserOperationExecutor.kt$UpdateUserOperationExecutor$private val _userBackend: IUserBackendService
ConstructorParameterNaming:UserBackendService.kt$UserBackendService$private val _httpClient: IHttpClient
- ConstructorParameterNaming:UserManager.kt$UserManager$private val _customEventController: ICustomEventController
ConstructorParameterNaming:UserManager.kt$UserManager$private val _identityModelStore: IdentityModelStore
ConstructorParameterNaming:UserManager.kt$UserManager$private val _languageContext: ILanguageContext
ConstructorParameterNaming:UserManager.kt$UserManager$private val _propertiesModelStore: PropertiesModelStore
@@ -159,30 +157,26 @@
ForbiddenComment:HttpClient.kt$HttpClient$// TODO: SHOULD RETURN OK INSTEAD OF NOT_MODIFIED TO MAKE TRANSPARENT?
ForbiddenComment:IPreferencesService.kt$PreferenceOneSignalKeys$* (String) The serialized IAMs TODO: This isn't currently used, determine if actually needed for cold start IAM fetch delay
ForbiddenComment:IUserBackendService.kt$IUserBackendService$// TODO: Change to send only the push subscription, optimally
- ForbiddenComment:OneSignalImp.kt$OneSignalImp$// TODO: Set JWT Token for all future requests.
- ForbiddenComment:OneSignalImp.kt$OneSignalImp$// TODO: remove JWT Token for all future requests.
+ ForbiddenComment:LoginHelper.kt$LoginHelper$// TODO: Set JWT Token for all future requests.
+ ForbiddenComment:LogoutHelper.kt$LogoutHelper$// TODO: remove JWT Token for all future requests.
ForbiddenComment:OperationRepo.kt$OperationRepo$// TODO: Need to provide callback for app to reset JWT. For now, fail with no retry.
ForbiddenComment:ParamsBackendService.kt$ParamsBackendService$// TODO: New
ForbiddenComment:PermissionsActivity.kt$PermissionsActivity$// TODO after we remove IAM from being an activity window we may be able to remove this handler
ForbiddenComment:PermissionsActivity.kt$PermissionsActivity$// TODO improve this method
- ForbiddenComment:PermissionsActivity.kt$PermissionsActivity.Companion$// TODO this will be removed once the handled is deleted
+ ForbiddenComment:PermissionsViewModel.kt$PermissionsViewModel.Companion$// TODO this will be removed once the handler is deleted
ForbiddenComment:SubscriptionOperationExecutor.kt$SubscriptionOperationExecutor$// TODO: whenever the end-user changes users, we need to add the read-your-write token here, currently no code to handle the re-fetch IAMs
ForbiddenComment:TrackGooglePurchase.kt$TrackGooglePurchase$// TODO: Handle very large list. Test for continuationToken != null then call getPurchases again
FunctionOnlyReturningConstant:AndroidUtils.kt$AndroidUtils$@Keep fun opaqueHasClass(_class: Class<*>): Boolean
FunctionParameterNaming:AndroidUtils.kt$AndroidUtils$_class: Class<*>
FunctionParameterNaming:JSONUtils.kt$JSONUtils$`object`: Any
- GlobalCoroutineUsage:HttpClient.kt$HttpClient$GlobalScope.launch(Dispatchers.IO) { var httpResponse = -1 var con: HttpURLConnection? = null if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { TrafficStats.setThreadStatsTag(THREAD_ID) } try { con = _connectionFactory.newHttpURLConnection(url) // https://github.com/OneSignal/OneSignal-Android-SDK/issues/1465 // Android 4.4 and older devices fail to register to onesignal.com to due it's TLS1.2+ requirement if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1 && con is HttpsURLConnection) { val conHttps = con conHttps.sslSocketFactory = TLS12SocketFactory( conHttps.sslSocketFactory, ) } con.useCaches = false con.connectTimeout = timeout con.readTimeout = timeout con.setRequestProperty(HTTP_SDK_VERSION_HEADER_KEY, HTTP_SDK_VERSION_HEADER_VALUE) if (OneSignalWrapper.sdkType != null && OneSignalWrapper.sdkVersion != null) { con.setRequestProperty("SDK-Wrapper", "onesignal/${OneSignalWrapper.sdkType}/${OneSignalWrapper.sdkVersion}") } con.setRequestProperty("Accept", OS_ACCEPT_HEADER) val subscriptionId = _configModelStore.model.pushSubscriptionId if (subscriptionId != null && subscriptionId.isNotEmpty()) { con.setRequestProperty("OneSignal-Subscription-Id", subscriptionId) } con.setRequestProperty("OneSignal-Install-Id", _installIdService.getId().toString()) if (jsonBody != null) { con.doInput = true } if (method != null) { con.setRequestProperty("Content-Type", "application/json; charset=UTF-8") con.requestMethod = method con.doOutput = true } logHTTPSent(con.requestMethod, con.url, jsonBody, con.requestProperties) if (jsonBody != null) { val strJsonBody = JSONUtils.toUnescapedEUIDString(jsonBody) val sendBytes = strJsonBody.toByteArray(charset("UTF-8")) con.setFixedLengthStreamingMode(sendBytes.size) val outputStream = con.outputStream outputStream.write(sendBytes) } // H E A D E R S if (headers?.cacheKey != null) { val eTag = _prefs.getString( PreferenceStores.ONESIGNAL, PreferenceOneSignalKeys.PREFS_OS_ETAG_PREFIX + headers.cacheKey, ) if (eTag != null) { con.setRequestProperty("If-None-Match", eTag) Logging.debug("HttpClient: Adding header if-none-match: $eTag") } } if (headers?.rywToken != null) { con.setRequestProperty("OneSignal-RYW-Token", headers.rywToken.toString()) } if (headers?.retryCount != null) { con.setRequestProperty("Onesignal-Retry-Count", headers.retryCount.toString()) } if (headers?.sessionDuration != null) { con.setRequestProperty("OneSignal-Session-Duration", headers.sessionDuration.toString()) } // Network request is made from getResponseCode() httpResponse = con.responseCode val retryAfter = retryAfterFromResponse(con) val retryLimit = retryLimitFromResponse(con) val newDelayUntil = _time.currentTimeMillis + (retryAfter ?: 0) * 1_000 if (newDelayUntil > delayNewRequestsUntil) delayNewRequestsUntil = newDelayUntil when (httpResponse) { HttpURLConnection.HTTP_NOT_MODIFIED -> { val cachedResponse = _prefs.getString( PreferenceStores.ONESIGNAL, PreferenceOneSignalKeys.PREFS_OS_HTTP_CACHE_PREFIX + headers?.cacheKey, ) Logging.debug( "HttpClient: Got Response = ${method ?: "GET"} ${con.url} - Using Cached response due to 304: " + cachedResponse, ) // TODO: SHOULD RETURN OK INSTEAD OF NOT_MODIFIED TO MAKE TRANSPARENT? retVal = HttpResponse(httpResponse, cachedResponse, retryAfterSeconds = retryAfter, retryLimit = retryLimit) } HttpURLConnection.HTTP_ACCEPTED, HttpURLConnection.HTTP_CREATED, HttpURLConnection.HTTP_OK -> { val inputStream = con.inputStream val scanner = Scanner(inputStream, "UTF-8") val json = if (scanner.useDelimiter("\\A").hasNext()) scanner.next() else "" scanner.close() Logging.debug( "HttpClient: Got Response = ${method ?: "GET"} ${con.url} - STATUS: $httpResponse - Body: " + json, ) if (headers?.cacheKey != null) { val eTag = con.getHeaderField("etag") if (eTag != null) { Logging.debug("HttpClient: Got Response = Response has etag of $eTag so caching the response.") _prefs.saveString( PreferenceStores.ONESIGNAL, PreferenceOneSignalKeys.PREFS_OS_ETAG_PREFIX + headers.cacheKey, eTag, ) _prefs.saveString( PreferenceStores.ONESIGNAL, PreferenceOneSignalKeys.PREFS_OS_HTTP_CACHE_PREFIX + headers.cacheKey, json, ) } } retVal = HttpResponse(httpResponse, json, retryAfterSeconds = retryAfter, retryLimit = retryLimit) } else -> { Logging.debug("HttpClient: Got Response = ${method ?: "GET"} ${con.url} - FAILED STATUS: $httpResponse") var inputStream = con.errorStream if (inputStream == null) { inputStream = con.inputStream } var jsonResponse: String? = null if (inputStream != null) { val scanner = Scanner(inputStream, "UTF-8") jsonResponse = if (scanner.useDelimiter("\\A").hasNext()) scanner.next() else "" scanner.close() Logging.warn("HttpClient: Got Response = $method - STATUS: $httpResponse - Body: $jsonResponse") } else { Logging.warn("HttpClient: Got Response = $method - STATUS: $httpResponse - No response body!") } retVal = HttpResponse(httpResponse, jsonResponse, retryAfterSeconds = retryAfter, retryLimit = retryLimit) } } } catch (t: Throwable) { if (t is ConnectException || t is UnknownHostException) { Logging.info("HttpClient: Could not send last request, device is offline. Throwable: " + t.javaClass.name) } else { Logging.warn("HttpClient: $method Error thrown from network stack. ", t) } retVal = HttpResponse(httpResponse, null, t) } finally { con?.disconnect() } }
- GlobalCoroutineUsage:PreferencesService.kt$PreferencesService$GlobalScope.async(Dispatchers.IO) { var lastSyncTime = _time.currentTimeMillis while (true) { try { // go through all outstanding items to process for (storeKey in prefsToApply.keys) { val storeMap = prefsToApply[storeKey]!! val prefsToWrite = getSharedPrefsByName(storeKey) if (prefsToWrite == null) { // the assumption here is there is no context yet, but will be. So ensure // we wake up to try again and persist the preference. waiter.wake() continue } val editor = prefsToWrite.edit() synchronized(storeMap) { for (key in storeMap.keys) { when (val value = storeMap[key]) { is String -> editor.putString(key, value as String?) is Boolean -> editor.putBoolean(key, (value as Boolean?)!!) is Int -> editor.putInt(key, (value as Int?)!!) is Long -> editor.putLong(key, (value as Long?)!!) is Set<*> -> editor.putStringSet(key, value as Set<String?>?) null -> editor.remove(key) } } storeMap.clear() } editor.apply() } // potentially delay to prevent this from constant IO if a bunch of // preferences are set sequentially. val newTime = _time.currentTimeMillis val delay = lastSyncTime - newTime + WRITE_CALL_DELAY_TO_BUFFER_MS lastSyncTime = newTime if (delay > 0) { delay(delay) } // wait to be woken up for the next pass waiter.waitForWake() } catch (e: Throwable) { Logging.log(LogLevel.ERROR, "Error with Preference work loop", e) } } }
- GlobalCoroutineUsage:RecoverFromDroppedLoginBug.kt$RecoverFromDroppedLoginBug$GlobalScope.launch(Dispatchers.IO) { _operationRepo.awaitInitialized() if (isInBadState()) { Logging.warn( "User with externalId:" + "${_identityModelStore.model.externalId} " + "was in a bad state, causing it to not update on OneSignal's " + "backend! We are recovering and replaying all unsent " + "operations now.", ) recoverByAddingBackDroppedLoginOperation() } }
InstanceOfCheckForException:HttpClient.kt$HttpClient$t is ConnectException
InstanceOfCheckForException:HttpClient.kt$HttpClient$t is UnknownHostException
LongMethod:ApplicationService.kt$ApplicationService$override suspend fun waitUntilSystemConditionsAvailable(): Boolean
LongMethod:ConfigModelStoreListener.kt$ConfigModelStoreListener$private fun fetchParams()
- LongMethod:HttpClient.kt$HttpClient$@OptIn(DelicateCoroutinesApi::class) private suspend fun makeRequestIODispatcher( url: String, method: String?, jsonBody: JSONObject?, timeout: Int, headers: OptionalHeaders?, ): HttpResponse
+ LongMethod:HttpClient.kt$HttpClient$private suspend fun makeRequestIODispatcher( url: String, method: String?, jsonBody: JSONObject?, timeout: Int, headers: OptionalHeaders?, ): HttpResponse
LongMethod:IdentityOperationExecutor.kt$IdentityOperationExecutor$override suspend fun execute(operations: List<Operation>): ExecutionResponse
LongMethod:LoginUserOperationExecutor.kt$LoginUserOperationExecutor$private suspend fun createUser( createUserOperation: LoginUserOperation, operations: List<Operation>, ): ExecutionResponse
LongMethod:LoginUserOperationExecutor.kt$LoginUserOperationExecutor$private suspend fun loginUser( loginUserOp: LoginUserOperation, operations: List<Operation>, ): ExecutionResponse
- LongMethod:OneSignalImp.kt$OneSignalImp$override fun initWithContext( context: Context, appId: String?, ): Boolean
LongMethod:OperationRepo.kt$OperationRepo$internal suspend fun executeOperations(ops: List<OperationQueueItem>)
LongMethod:OutcomeEventsController.kt$OutcomeEventsController$private suspend fun sendAndCreateOutcomeEvent( name: String, weight: Float, // Note: this is optional sessionTime: Long, influences: List<Influence>, ): OutcomeEvent?
LongMethod:OutcomeEventsController.kt$OutcomeEventsController$private suspend fun sendUniqueOutcomeEvent( name: String, sessionInfluences: List<Influence>, ): OutcomeEvent?
@@ -197,14 +191,15 @@
LongMethod:TrackGooglePurchase.kt$TrackGooglePurchase$private fun queryBoughtItems()
LongMethod:TrackGooglePurchase.kt$TrackGooglePurchase$private fun sendPurchases( skusToAdd: ArrayList<String>, newPurchaseTokens: ArrayList<String>, )
LongMethod:UpdateUserOperationExecutor.kt$UpdateUserOperationExecutor$override suspend fun execute(operations: List<Operation>): ExecutionResponse
- LongParameterList:ICustomEventBackendService.kt$ICustomEventBackendService$( appId: String, onesignalId: String, externalId: String?, timestamp: Long, eventName: String, eventProperties: String?, metadata: CustomEventMetadata, )
LongParameterList:IDatabase.kt$IDatabase$( table: String, columns: Array<String>? = null, whereClause: String? = null, whereArgs: Array<String>? = null, groupBy: String? = null, having: String? = null, orderBy: String? = null, limit: String? = null, action: (ICursor) -> Unit, )
LongParameterList:IOutcomeEventsBackendService.kt$IOutcomeEventsBackendService$( appId: String, userId: String, subscriptionId: String, deviceType: String, direct: Boolean?, event: OutcomeEvent, )
+ LongParameterList:IParamsBackendService.kt$ParamsObject$( var googleProjectNumber: String? = null, var enterprise: Boolean? = null, var useIdentityVerification: Boolean? = null, var notificationChannels: JSONArray? = null, var firebaseAnalytics: Boolean? = null, var restoreTTLFilter: Boolean? = null, var clearGroupOnSummaryClick: Boolean? = null, var receiveReceiptEnabled: Boolean? = null, var disableGMSMissingPrompt: Boolean? = null, var unsubscribeWhenNotificationsDisabled: Boolean? = null, var locationShared: Boolean? = null, var requiresUserPrivacyConsent: Boolean? = null, var opRepoExecutionInterval: Long? = null, var influenceParams: InfluenceParamsObject, var fcmParams: FCMParamsObject, )
LongParameterList:IUserBackendService.kt$IUserBackendService$( appId: String, aliasLabel: String, aliasValue: String, properties: PropertiesObject, refreshDeviceMetadata: Boolean, propertyiesDelta: PropertiesDeltasObject, )
LongParameterList:LoginUserOperationExecutor.kt$LoginUserOperationExecutor$( private val _identityOperationExecutor: IdentityOperationExecutor, private val _application: IApplicationService, private val _deviceService: IDeviceService, private val _userBackend: IUserBackendService, private val _identityModelStore: IdentityModelStore, private val _propertiesModelStore: PropertiesModelStore, private val _subscriptionsModelStore: SubscriptionModelStore, private val _configModelStore: ConfigModelStore, private val _languageContext: ILanguageContext, )
LongParameterList:OutcomeEventsController.kt$OutcomeEventsController$( private val _session: ISessionService, private val _influenceManager: IInfluenceManager, private val _outcomeEventsCache: IOutcomeEventsRepository, private val _outcomeEventsPreferences: IOutcomeEventsPreferences, private val _outcomeEventsBackend: IOutcomeEventsBackendService, private val _configModelStore: ConfigModelStore, private val _identityModelStore: IdentityModelStore, private val _subscriptionManager: ISubscriptionManager, private val _deviceService: IDeviceService, private val _time: ITime, )
LongParameterList:SubscriptionObject.kt$SubscriptionObject$( val id: String? = null, val type: SubscriptionObjectType? = null, val token: String? = null, val enabled: Boolean? = null, val notificationTypes: Int? = null, val sdk: String? = null, val deviceModel: String? = null, val deviceOS: String? = null, val rooted: Boolean? = null, val netType: Int? = null, val carrier: String? = null, val appVersion: String? = null, )
LongParameterList:SubscriptionOperationExecutor.kt$SubscriptionOperationExecutor$( private val _subscriptionBackend: ISubscriptionBackendService, private val _deviceService: IDeviceService, private val _applicationService: IApplicationService, private val _subscriptionModelStore: SubscriptionModelStore, private val _configModelStore: ConfigModelStore, private val _buildUserService: IRebuildUserService, private val _newRecordState: NewRecordsState, private val _consistencyManager: IConsistencyManager, )
+ LongParameterList:UserSwitcher.kt$UserSwitcher$( private val preferencesService: IPreferencesService, private val operationRepo: IOperationRepo, private val services: ServiceProvider, private val idManager: IDManager = IDManager, private val identityModelStore: IdentityModelStore, private val propertiesModelStore: PropertiesModelStore, private val subscriptionModelStore: SubscriptionModelStore, private val configModel: ConfigModel, private val oneSignalUtils: OneSignalUtils = OneSignalUtils, private val carrierName: String? = null, private val deviceOS: String? = null, private val androidUtils: AndroidUtils = AndroidUtils, private val appContextProvider: () -> Context, )
LoopWithTooManyJumpStatements:ModelStore.kt$ModelStore$for (index in jsonArray.length() - 1 downTo 0) { val newModel = create(jsonArray.getJSONObject(index)) ?: continue /* * NOTE: Migration fix for bug introduced in 5.1.12 * The following check is intended for the operation model store. * When the call to this method moved out of the operation model store's initializer, * duplicate operations could be cached. * See https://github.com/OneSignal/OneSignal-Android-SDK/pull/2099 */ val hasExisting = models.any { it.id == newModel.id } if (hasExisting) { Logging.debug("ModelStore<$name>: load - operation.id: ${newModel.id} already exists in the store.") continue } models.add(0, newModel) // listen for changes to this model newModel.subscribe(this) }
MagicNumber:ApplicationService.kt$ApplicationService$50
MagicNumber:BackgroundManager.kt$BackgroundManager$5000
@@ -231,6 +226,7 @@
MagicNumber:OSDatabase.kt$OSDatabase$7
MagicNumber:OSDatabase.kt$OSDatabase$8
MagicNumber:OSDatabase.kt$OSDatabase$9
+ MagicNumber:OneSignalDispatchers.kt$OneSignalDispatchers$1024
MagicNumber:OperationRepo.kt$OperationRepo$1_000
MagicNumber:OutcomeEventsController.kt$OutcomeEventsController$1000
MagicNumber:PermissionsActivity.kt$PermissionsActivity$23
@@ -277,11 +273,11 @@
PrintStackTrace:DeviceUtils.kt$DeviceUtils$t
PrintStackTrace:JSONUtils.kt$JSONUtils$e
PrintStackTrace:OSDatabase.kt$OSDatabase$e
- PrintStackTrace:OneSignalImp.kt$OneSignalImp$e
PrintStackTrace:OutcomeTableProvider.kt$OutcomeTableProvider$e
PrintStackTrace:TrackGooglePurchase.kt$TrackGooglePurchase$e
PrintStackTrace:TrackGooglePurchase.kt$TrackGooglePurchase.<no name provided>$t
RethrowCaughtException:OSDatabase.kt$OSDatabase$throw e
+ ReturnCount:AppIdResolution.kt$fun resolveAppId( inputAppId: String?, configModel: ConfigModel, preferencesService: IPreferencesService, ): AppIdResolution
ReturnCount:BackgroundManager.kt$BackgroundManager$override fun cancelRunBackgroundServices(): Boolean
ReturnCount:ConfigModel.kt$ConfigModel$override fun createModelForProperty( property: String, jsonObject: JSONObject, ): Model?
ReturnCount:HttpClient.kt$HttpClient$private suspend fun makeRequest( url: String, method: String?, jsonBody: JSONObject?, timeout: Int, headers: OptionalHeaders?, ): HttpResponse
@@ -295,12 +291,12 @@
ReturnCount:Model.kt$Model$protected fun getOptIntProperty( name: String, create: (() -> Int?)? = null, ): Int?
ReturnCount:Model.kt$Model$protected fun getOptLongProperty( name: String, create: (() -> Long?)? = null, ): Long?
ReturnCount:Model.kt$Model$protected inline fun <reified T : Enum<T>> getOptEnumProperty(name: String): T?
- ReturnCount:OneSignalImp.kt$OneSignalImp$override fun initWithContext( context: Context, appId: String?, ): Boolean
ReturnCount:OperationModelStore.kt$OperationModelStore$override fun create(jsonObject: JSONObject?): Operation?
ReturnCount:OperationModelStore.kt$OperationModelStore$private fun isValidOperation(jsonObject: JSONObject): Boolean
ReturnCount:OutcomeEventsController.kt$OutcomeEventsController$private suspend fun sendAndCreateOutcomeEvent( name: String, weight: Float, // Note: this is optional sessionTime: Long, influences: List<Influence>, ): OutcomeEvent?
ReturnCount:OutcomeEventsController.kt$OutcomeEventsController$private suspend fun sendUniqueOutcomeEvent( name: String, sessionInfluences: List<Influence>, ): OutcomeEvent?
- ReturnCount:PermissionsActivity.kt$PermissionsActivity$private fun shouldShowSettings(permission: String): Boolean
+ ReturnCount:PermissionsViewModel.kt$PermissionsViewModel$private fun shouldShowSettings( permission: String, shouldShowRationaleAfter: Boolean, ): Boolean
+ ReturnCount:PermissionsViewModel.kt$PermissionsViewModel$suspend fun initialize( activity: Activity, permissionType: String?, androidPermission: String?, ): Boolean
ReturnCount:PreferenceStoreFix.kt$PreferenceStoreFix$fun ensureNoObfuscatedPrefStore(context: Context)
ReturnCount:PreferencesService.kt$PreferencesService$private fun get( store: String, key: String, type: Class<*>, defValue: Any?, ): Any?
ReturnCount:PropertiesModelStoreListener.kt$PropertiesModelStoreListener$override fun getUpdateOperation( model: PropertiesModel, path: String, property: String, oldValue: Any?, newValue: Any?, ): Operation?
@@ -312,7 +308,6 @@
SpreadOperator:AndroidUtils.kt$AndroidUtils$(*packageInfo.requestedPermissions)
SpreadOperator:ServiceRegistration.kt$ServiceRegistrationReflection$(*paramList.toTypedArray())
StringLiteralDuplication:OSDatabase.kt$OSDatabase$"Error closing transaction! "
- StringLiteralDuplication:OneSignalImp.kt$OneSignalImp$"Must call 'initWithContext' before use"
StringLiteralDuplication:OutcomesDbContract.kt$OutcomesDbContract$"CREATE TABLE "
SwallowedException:AlertDialogPrepromptForAndroidSettings.kt$AlertDialogPrepromptForAndroidSettings$ex: BadTokenException
SwallowedException:AndroidUtils.kt$AndroidUtils$e: PackageManager.NameNotFoundException
@@ -322,17 +317,21 @@
SwallowedException:JSONUtils.kt$JSONUtils$t: Throwable
SwallowedException:PermissionsActivity.kt$PermissionsActivity$e: ClassNotFoundException
SwallowedException:PreferencesService.kt$PreferencesService$ex: Exception
+ SwallowedException:SyncJobService.kt$SyncJobService$e: Exception
SwallowedException:TrackGooglePurchase.kt$TrackGooglePurchase.Companion$t: Throwable
+ ThrowsCount:OneSignalImp.kt$OneSignalImp$private suspend fun waitUntilInitInternal(operationName: String? = null)
TooGenericExceptionCaught:AndroidUtils.kt$AndroidUtils$e: Throwable
TooGenericExceptionCaught:DeviceUtils.kt$DeviceUtils$t: Throwable
TooGenericExceptionCaught:HttpClient.kt$HttpClient$e: Throwable
TooGenericExceptionCaught:HttpClient.kt$HttpClient$t: Throwable
TooGenericExceptionCaught:JSONUtils.kt$JSONUtils$t: Throwable
TooGenericExceptionCaught:Logging.kt$Logging$t: Throwable
+ TooGenericExceptionCaught:OneSignalDispatchers.kt$OneSignalDispatchers$e: Exception
TooGenericExceptionCaught:OperationRepo.kt$OperationRepo$e: Throwable
TooGenericExceptionCaught:PreferenceStoreFix.kt$PreferenceStoreFix$e: Throwable
TooGenericExceptionCaught:PreferencesService.kt$PreferencesService$e: Throwable
TooGenericExceptionCaught:PreferencesService.kt$PreferencesService$ex: Exception
+ TooGenericExceptionCaught:SyncJobService.kt$SyncJobService$e: Exception
TooGenericExceptionCaught:ThreadUtils.kt$e: Exception
TooGenericExceptionCaught:TrackGooglePurchase.kt$TrackGooglePurchase$e: Throwable
TooGenericExceptionCaught:TrackGooglePurchase.kt$TrackGooglePurchase$t: Throwable
@@ -346,14 +345,11 @@
TooGenericExceptionThrown:LoginUserOperationExecutor.kt$LoginUserOperationExecutor$throw Exception("Unrecognized operation: $startingOp")
TooGenericExceptionThrown:Model.kt$Model$throw Exception("If parent model is set, parent property must also be set.")
TooGenericExceptionThrown:Model.kt$Model$throw Exception("If parent property is set, parent model must also be set.")
- TooGenericExceptionThrown:OneSignalImp.kt$OneSignalImp$throw Exception( "Must call 'initWithContext' before use", )
- TooGenericExceptionThrown:OneSignalImp.kt$OneSignalImp$throw Exception("Must call 'initWithContext' before 'login'")
- TooGenericExceptionThrown:OneSignalImp.kt$OneSignalImp$throw Exception("Must call 'initWithContext' before 'logout'")
TooGenericExceptionThrown:OperationModelStore.kt$OperationModelStore$throw Exception("Unrecognized operation: $operationName")
TooGenericExceptionThrown:OperationRepo.kt$OperationRepo$throw Exception("Both comparison keys can not be blank!")
TooGenericExceptionThrown:OperationRepo.kt$OperationRepo$throw Exception("Could not find executor for operation ${startingOp.operation.name}")
TooGenericExceptionThrown:PermissionsActivity.kt$PermissionsActivity$throw RuntimeException( "Could not find callback class for PermissionActivity: $className", )
- TooGenericExceptionThrown:PermissionsActivity.kt$PermissionsActivity$throw RuntimeException("Missing handler for permissionRequestType: $permissionRequestType")
+ TooGenericExceptionThrown:PermissionsViewModel.kt$PermissionsViewModel$throw RuntimeException("Missing handler for permissionRequestType: $type")
TooGenericExceptionThrown:PreferencesService.kt$PreferencesService$throw Exception("Store not found: $store")
TooGenericExceptionThrown:RefreshUserOperationExecutor.kt$RefreshUserOperationExecutor$throw Exception("Unrecognized operation(s)! Attempted operations:\n$operations")
TooGenericExceptionThrown:RefreshUserOperationExecutor.kt$RefreshUserOperationExecutor$throw Exception("Unrecognized operation: $startingOp")
@@ -370,14 +366,16 @@
TooManyFunctions:ApplicationService.kt$ApplicationService : IApplicationServiceActivityLifecycleCallbacksOnGlobalLayoutListener
TooManyFunctions:BackgroundManager.kt$BackgroundManager : IApplicationLifecycleHandlerIBackgroundManagerIStartableService
TooManyFunctions:HttpClient.kt$HttpClient : IHttpClient
+ TooManyFunctions:IOneSignal.kt$IOneSignal
TooManyFunctions:IUserManager.kt$IUserManager
TooManyFunctions:InfluenceManager.kt$InfluenceManager : IInfluenceManagerISessionLifecycleHandler
TooManyFunctions:JSONObjectExtensions.kt$com.onesignal.common.JSONObjectExtensions.kt
- TooManyFunctions:JSONUtils.kt$JSONUtils$JSONUtils
TooManyFunctions:Logging.kt$Logging$Logging
TooManyFunctions:Model.kt$Model : IEventNotifier
TooManyFunctions:ModelStore.kt$ModelStore<TModel> : IEventNotifierIModelStoreIModelChangedHandler
TooManyFunctions:OSDatabase.kt$OSDatabase : SQLiteOpenHelperIDatabase
+ TooManyFunctions:OneSignal.kt$OneSignal$OneSignal
+ TooManyFunctions:OneSignalImp.kt$OneSignalImp : IOneSignalIServiceProvider
TooManyFunctions:OperationRepo.kt$OperationRepo : IOperationRepoIStartableService
TooManyFunctions:OutcomeEventsController.kt$OutcomeEventsController : IOutcomeEventsControllerIStartableServiceISessionLifecycleHandler
TooManyFunctions:PreferencesService.kt$PreferencesService : IPreferencesServiceIStartableService
@@ -386,6 +384,7 @@
UndocumentedPublicClass:AlertDialogPrepromptForAndroidSettings.kt$AlertDialogPrepromptForAndroidSettings$Callback
UndocumentedPublicClass:AndroidUtils.kt$AndroidUtils
UndocumentedPublicClass:AndroidUtils.kt$AndroidUtils$SchemaType
+ UndocumentedPublicClass:AppIdResolution.kt$AppIdResolution
UndocumentedPublicClass:ApplicationService.kt$ApplicationService : IApplicationServiceActivityLifecycleCallbacksOnGlobalLayoutListener
UndocumentedPublicClass:ConfigModel.kt$ConfigModel : Model
UndocumentedPublicClass:ConfigModelStore.kt$ConfigModelStore : SingletonModelStore
@@ -409,6 +408,10 @@
UndocumentedPublicClass:IOperationExecutor.kt$ExecutionResponse
UndocumentedPublicClass:IOperationExecutor.kt$ExecutionResult
UndocumentedPublicClass:IOutcomeEvent.kt$IOutcomeEvent
+ UndocumentedPublicClass:IParamsBackendService.kt$FCMParamsObject
+ UndocumentedPublicClass:IParamsBackendService.kt$IParamsBackendService
+ UndocumentedPublicClass:IParamsBackendService.kt$InfluenceParamsObject
+ UndocumentedPublicClass:IParamsBackendService.kt$ParamsObject
UndocumentedPublicClass:IPreferencesService.kt$PreferenceOneSignalKeys
UndocumentedPublicClass:IPreferencesService.kt$PreferencePlayerPurchasesKeys
UndocumentedPublicClass:IPreferencesService.kt$PreferenceStores
@@ -426,10 +429,11 @@
UndocumentedPublicClass:JSONConverter.kt$JSONConverter
UndocumentedPublicClass:JSONUtils.kt$JSONUtils
UndocumentedPublicClass:Logging.kt$Logging
+ UndocumentedPublicClass:LoginHelper.kt$LoginHelper
+ UndocumentedPublicClass:LogoutHelper.kt$LogoutHelper
UndocumentedPublicClass:MigrationRecovery.kt$MigrationRecovery : IMigrationRecovery
UndocumentedPublicClass:NetworkUtils.kt$NetworkUtils
UndocumentedPublicClass:NetworkUtils.kt$NetworkUtils$ResponseStatusType
- UndocumentedPublicClass:OSPrimaryCoroutineScope.kt$OSPrimaryCoroutineScope
UndocumentedPublicClass:OneSignalDbContract.kt$OneSignalDbContract
UndocumentedPublicClass:OneSignalDbContract.kt$OneSignalDbContract$InAppMessageTable : BaseColumns
UndocumentedPublicClass:OneSignalDbContract.kt$OneSignalDbContract$NotificationTable : BaseColumns
@@ -437,7 +441,6 @@
UndocumentedPublicClass:OneSignalWrapper.kt$OneSignalWrapper
UndocumentedPublicClass:Operation.kt$GroupComparisonType
UndocumentedPublicClass:OptionalHeaders.kt$OptionalHeaders
- UndocumentedPublicClass:PermissionsActivity.kt$PermissionsActivity : Activity
UndocumentedPublicClass:PreferenceStoreFix.kt$PreferenceStoreFix
UndocumentedPublicClass:PropertiesDeltasObject.kt$PropertiesDeltasObject
UndocumentedPublicClass:PropertiesDeltasObject.kt$PurchaseObject
@@ -458,8 +461,10 @@
UndocumentedPublicClass:SyncJobService.kt$SyncJobService : JobService
UndocumentedPublicClass:TimeUtils.kt$TimeUtils
UndocumentedPublicClass:UserRefreshService.kt$UserRefreshService : IStartableServiceISessionLifecycleHandler
+ UndocumentedPublicClass:UserSwitcher.kt$UserSwitcher
UndocumentedPublicClass:ViewUtils.kt$ViewUtils
UndocumentedPublicFunction:AlertDialogPrepromptForAndroidSettings.kt$AlertDialogPrepromptForAndroidSettings$fun show( activity: Activity, titlePrefix: String, previouslyDeniedPostfix: String, callback: Callback, )
+ UndocumentedPublicFunction:AlertDialogPrepromptForAndroidSettings.kt$AlertDialogPrepromptForAndroidSettings$fun show( activity: Activity, titlePrefix: String, previouslyDeniedPostfix: String, callback: Callback, dismissCallback: (() -> Unit)?, )
UndocumentedPublicFunction:AlertDialogPrepromptForAndroidSettings.kt$AlertDialogPrepromptForAndroidSettings.Callback$fun onAccept()
UndocumentedPublicFunction:AlertDialogPrepromptForAndroidSettings.kt$AlertDialogPrepromptForAndroidSettings.Callback$fun onDecline()
UndocumentedPublicFunction:AndroidUtils.kt$AndroidUtils$@Keep fun opaqueHasClass(_class: Class<*>): Boolean
@@ -482,6 +487,7 @@
UndocumentedPublicFunction:AndroidUtils.kt$AndroidUtils$fun openURLInBrowser( appContext: Context, url: String, )
UndocumentedPublicFunction:AndroidUtils.kt$AndroidUtils$fun openURLInBrowserIntent(uri: Uri): Intent
UndocumentedPublicFunction:AndroidUtils.kt$AndroidUtils.SchemaType.Companion$fun fromString(text: String?): SchemaType?
+ UndocumentedPublicFunction:AppIdResolution.kt$fun resolveAppId( inputAppId: String?, configModel: ConfigModel, preferencesService: IPreferencesService, ): AppIdResolution
UndocumentedPublicFunction:ApplicationService.kt$ApplicationService$fun decorViewReady( activity: Activity, runnable: Runnable, )
UndocumentedPublicFunction:DateUtils.kt$DateUtils$fun iso8601Format(): SimpleDateFormat
UndocumentedPublicFunction:DeviceUtils.kt$DeviceUtils$fun getCarrierName(appContext: Context): String?
@@ -538,6 +544,8 @@
UndocumentedPublicFunction:Logging.kt$Logging$@JvmStatic fun warn( message: String, throwable: Throwable? = null, )
UndocumentedPublicFunction:Logging.kt$Logging$fun addListener(listener: ILogListener)
UndocumentedPublicFunction:Logging.kt$Logging$fun removeListener(listener: ILogListener)
+ UndocumentedPublicFunction:LoginHelper.kt$LoginHelper$suspend fun login( externalId: String, jwtBearerToken: String? = null, )
+ UndocumentedPublicFunction:LogoutHelper.kt$LogoutHelper$fun logout()
UndocumentedPublicFunction:Model.kt$Model$fun <T> setListProperty( name: String, value: List<T>, tag: String = ModelChangeTags.NORMAL, forceChange: Boolean = false, )
UndocumentedPublicFunction:Model.kt$Model$fun <T> setMapModelProperty( name: String, value: MapModel<T>, tag: String = ModelChangeTags.NORMAL, forceChange: Boolean = false, )
UndocumentedPublicFunction:Model.kt$Model$fun <T> setOptListProperty( name: String, value: List<T>?, tag: String = ModelChangeTags.NORMAL, forceChange: Boolean = false, )
@@ -564,7 +572,8 @@
UndocumentedPublicFunction:NewRecordsState.kt$NewRecordsState$fun add(key: String)
UndocumentedPublicFunction:NewRecordsState.kt$NewRecordsState$fun canAccess(key: String): Boolean
UndocumentedPublicFunction:NewRecordsState.kt$NewRecordsState$fun isInMissingRetryWindow(key: String): Boolean
- UndocumentedPublicFunction:OSPrimaryCoroutineScope.kt$OSPrimaryCoroutineScope$suspend fun waitForIdle()
+ UndocumentedPublicFunction:OneSignalDispatchers.kt$OneSignalDispatchers$fun launchOnDefault(block: suspend () -> Unit): Job
+ UndocumentedPublicFunction:OneSignalDispatchers.kt$OneSignalDispatchers$fun launchOnIO(block: suspend () -> Unit): Job
UndocumentedPublicFunction:OneSignalUtils.kt$OneSignalUtils$fun isValidEmail(email: String): Boolean
UndocumentedPublicFunction:OneSignalUtils.kt$OneSignalUtils$fun isValidPhoneNumber(number: String): Boolean
UndocumentedPublicFunction:PushSubscriptionChangedState.kt$PushSubscriptionChangedState$fun toJSONObject(): JSONObject
@@ -579,6 +588,9 @@
UndocumentedPublicFunction:TimeUtils.kt$TimeUtils$fun getTimeZoneOffset(): Int
UndocumentedPublicFunction:UserChangedState.kt$UserChangedState$fun toJSONObject(): JSONObject
UndocumentedPublicFunction:UserState.kt$UserState$fun toJSONObject(): JSONObject
+ UndocumentedPublicFunction:UserSwitcher.kt$UserSwitcher$fun createAndSwitchToNewUser( suppressBackendOperation: Boolean = false, modify: ((identityModel: IdentityModel, propertiesModel: PropertiesModel) -> Unit)? = null, )
+ UndocumentedPublicFunction:UserSwitcher.kt$UserSwitcher$fun createPushSubscriptionFromLegacySync( legacyPlayerId: String, legacyUserSyncJSON: JSONObject, configModel: ConfigModel, subscriptionModelStore: SubscriptionModelStore, appContext: Context, ): Boolean
+ UndocumentedPublicFunction:UserSwitcher.kt$UserSwitcher$fun initUser(forceCreateUser: Boolean)
UndocumentedPublicFunction:ViewUtils.kt$ViewUtils$fun dpToPx(dp: Int): Int
UndocumentedPublicFunction:ViewUtils.kt$ViewUtils$fun getCutoutAndStatusBarInsets(activity: Activity): IntArray
UndocumentedPublicFunction:ViewUtils.kt$ViewUtils$fun getFullbleedWindowWidth(activity: Activity): Int
@@ -588,7 +600,11 @@
UnusedPrivateMember:AndroidUtils.kt$AndroidUtils$var requestPermission: String? = null
UnusedPrivateMember:ApplicationService.kt$ApplicationService$val listenerKey = "decorViewReady:$runnable"
UnusedPrivateMember:JSONUtils.kt$JSONUtils$`object`: Any
+ UnusedPrivateMember:LoginHelper.kt$LoginHelper$jwtBearerToken: String? = null
UnusedPrivateMember:OSDatabase.kt$OSDatabase.Companion$private const val FLOAT_TYPE = " FLOAT"
UnusedPrivateMember:OperationRepo.kt$OperationRepo$private val _time: ITime
+ UseCheckOrError:OneSignalImp.kt$OneSignalImp$throw IllegalStateException("'initWithContext failed' before 'login'")
+ UseCheckOrError:OneSignalImp.kt$OneSignalImp$throw IllegalStateException("'initWithContext failed' before 'logout'")
+ UseCheckOrError:OneSignalImp.kt$OneSignalImp$throw initFailureException ?: IllegalStateException("Initialization failed. Cannot proceed.")
diff --git a/OneSignalSDK/detekt/detekt-config.yml b/OneSignalSDK/detekt/detekt-config.yml
index de24a4b2b2..12c24e6464 100644
--- a/OneSignalSDK/detekt/detekt-config.yml
+++ b/OneSignalSDK/detekt/detekt-config.yml
@@ -91,7 +91,7 @@ comments:
UndocumentedPublicFunction:
active: true
excludes: ['**/test/**', '**/androidTest/**', '**/testhelpers/**']
-
+
EndOfSentenceFormat:
active: false
endOfSentenceFormat: ([.?!][ \t\n\r\f<])|([.?!:]$)
diff --git a/OneSignalSDK/onesignal/core/build.gradle b/OneSignalSDK/onesignal/core/build.gradle
index 8dd5c206da..6f90bb1224 100644
--- a/OneSignalSDK/onesignal/core/build.gradle
+++ b/OneSignalSDK/onesignal/core/build.gradle
@@ -82,8 +82,6 @@ dependencies {
}
}
- // Otel module dependency
- implementation(project(':OneSignal:otel'))
testImplementation(project(':OneSignal:testhelpers'))
testImplementation("io.kotest:kotest-runner-junit5:$kotestVersion")
diff --git a/OneSignalSDK/onesignal/core/src/main/AndroidManifest.xml b/OneSignalSDK/onesignal/core/src/main/AndroidManifest.xml
index 7d0c8323f0..285ce5c588 100644
--- a/OneSignalSDK/onesignal/core/src/main/AndroidManifest.xml
+++ b/OneSignalSDK/onesignal/core/src/main/AndroidManifest.xml
@@ -1,9 +1,4 @@
-
-
-
-
-
+
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/JSONUtils.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/JSONUtils.kt
index 9f400bb559..0cf3b0bdd1 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/JSONUtils.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/JSONUtils.kt
@@ -61,7 +61,7 @@ object JSONUtils {
try {
val value = jsonObject.opt(key)
if (value is JSONArray || value is JSONObject) {
- Logging.warn("Omitting key '$key'! sendTags DO NOT supported nested values!")
+ Logging.error("Omitting key '$key'! sendTags DO NOT supported nested values!")
} else if (jsonObject.isNull(key) || "" == value) {
result[key] = ""
} else {
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/CoreModule.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/CoreModule.kt
index 8897bb13a6..9083cddade 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/CoreModule.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/CoreModule.kt
@@ -33,7 +33,6 @@ import com.onesignal.core.internal.purchases.impl.TrackGooglePurchase
import com.onesignal.core.internal.startup.IStartableService
import com.onesignal.core.internal.time.ITime
import com.onesignal.core.internal.time.impl.Time
-import com.onesignal.debug.internal.crash.OneSignalCrashUploaderWrapper
import com.onesignal.inAppMessages.IInAppMessagesManager
import com.onesignal.inAppMessages.internal.MisconfiguredIAMManager
import com.onesignal.location.ILocationManager
@@ -82,9 +81,6 @@ internal class CoreModule : IModule {
// Purchase Tracking
builder.register().provides()
- // Crash Uploader (crash handler is initialized directly in OneSignalImp for early initialization)
- builder.register().provides()
-
// Register dummy services in the event they are not configured. These dummy services
// will throw an error message if the associated functionality is attempted to be used.
builder.register().provides()
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/backend/IParamsBackendService.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/backend/IParamsBackendService.kt
index 8773a23af3..514cc798bc 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/backend/IParamsBackendService.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/backend/IParamsBackendService.kt
@@ -2,7 +2,7 @@ package com.onesignal.core.internal.backend
import org.json.JSONArray
-internal interface IParamsBackendService {
+interface IParamsBackendService {
/**
* Retrieve the configuration parameters for the [appId] and optional [subscriptionId].
*
@@ -20,8 +20,7 @@ internal interface IParamsBackendService {
): ParamsObject
}
-@Suppress("LongParameterList")
-internal class ParamsObject(
+class ParamsObject(
var googleProjectNumber: String? = null,
var enterprise: Boolean? = null,
var useIdentityVerification: Boolean? = null,
@@ -37,10 +36,9 @@ internal class ParamsObject(
var opRepoExecutionInterval: Long? = null,
var influenceParams: InfluenceParamsObject,
var fcmParams: FCMParamsObject,
- val remoteLoggingParams: RemoteLoggingParamsObject,
)
-internal class InfluenceParamsObject(
+class InfluenceParamsObject(
val indirectNotificationAttributionWindow: Int? = null,
val notificationLimit: Int? = null,
val indirectIAMAttributionWindow: Int? = null,
@@ -50,13 +48,8 @@ internal class InfluenceParamsObject(
val isUnattributedEnabled: Boolean? = null,
)
-internal class FCMParamsObject(
+class FCMParamsObject(
val projectId: String? = null,
val appId: String? = null,
val apiKey: String? = null,
)
-
-internal class RemoteLoggingParamsObject(
- val logLevel: com.onesignal.debug.LogLevel? = null,
- val isEnabled: Boolean = logLevel != null,
-)
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/backend/impl/ParamsBackendService.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/backend/impl/ParamsBackendService.kt
index dfaaa027dc..85dd452d41 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/backend/impl/ParamsBackendService.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/backend/impl/ParamsBackendService.kt
@@ -11,7 +11,6 @@ import com.onesignal.core.internal.backend.FCMParamsObject
import com.onesignal.core.internal.backend.IParamsBackendService
import com.onesignal.core.internal.backend.InfluenceParamsObject
import com.onesignal.core.internal.backend.ParamsObject
-import com.onesignal.core.internal.backend.RemoteLoggingParamsObject
import com.onesignal.core.internal.http.CacheKeys
import com.onesignal.core.internal.http.IHttpClient
import com.onesignal.core.internal.http.impl.OptionalHeaders
@@ -58,16 +57,6 @@ internal class ParamsBackendService(
)
}
- // Process Remote Logging params
- var remoteLoggingParams: RemoteLoggingParamsObject? = null
- responseJson.expandJSONObject("logging_config") {
- val logLevel = LogLevel.fromString(it.safeString("log_level"))
- remoteLoggingParams =
- RemoteLoggingParamsObject(
- logLevel = logLevel,
- )
- }
-
return ParamsObject(
googleProjectNumber = responseJson.safeString("android_sender_id"),
enterprise = responseJson.safeBool("enterp"),
@@ -86,7 +75,6 @@ internal class ParamsBackendService(
opRepoExecutionInterval = responseJson.safeLong("oprepo_execution_interval"),
influenceParams = influenceParams ?: InfluenceParamsObject(),
fcmParams = fcmParams ?: FCMParamsObject(),
- remoteLoggingParams = remoteLoggingParams ?: RemoteLoggingParamsObject(),
)
}
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/background/impl/BackgroundManager.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/background/impl/BackgroundManager.kt
index 01c6c81934..eddb183784 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/background/impl/BackgroundManager.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/background/impl/BackgroundManager.kt
@@ -186,7 +186,7 @@ internal class BackgroundManager(
} catch (e: NullPointerException) {
// Catch for buggy Oppo devices
// https://github.com/OneSignal/OneSignal-Android-SDK/issues/487
- Logging.info(
+ Logging.error(
"scheduleSyncServiceAsJob called JobScheduler.jobScheduler which " +
"triggered an internal null Android error. Skipping job.",
e,
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/ConfigModel.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/ConfigModel.kt
index bd06e4c3e4..74d31c4669 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/ConfigModel.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/ConfigModel.kt
@@ -1,7 +1,6 @@
package com.onesignal.core.internal.config
import com.onesignal.common.modeling.Model
-import com.onesignal.core.internal.http.OneSignalService.ONESIGNAL_API_BASE_URL
import org.json.JSONArray
import org.json.JSONObject
@@ -37,7 +36,7 @@ class ConfigModel : Model() {
* The API URL String.
*/
var apiUrl: String
- get() = getStringProperty(::apiUrl.name) { ONESIGNAL_API_BASE_URL }
+ get() = getStringProperty(::apiUrl.name) { "https://api.onesignal.com/" }
set(value) {
setStringProperty(::apiUrl.name, value)
}
@@ -302,9 +301,6 @@ class ConfigModel : Model() {
val fcmParams: FCMConfigModel
get() = getAnyProperty(::fcmParams.name) { FCMConfigModel(this, ::fcmParams.name) } as FCMConfigModel
- val remoteLoggingParams: RemoteLoggingConfigModel
- get() = getAnyProperty(::remoteLoggingParams.name) { RemoteLoggingConfigModel(this, ::remoteLoggingParams.name) } as RemoteLoggingConfigModel
-
override fun createModelForProperty(
property: String,
jsonObject: JSONObject,
@@ -321,12 +317,6 @@ class ConfigModel : Model() {
return model
}
- if (property == ::remoteLoggingParams.name) {
- val model = RemoteLoggingConfigModel(this, ::remoteLoggingParams.name)
- model.initializeFromJson(jsonObject)
- return model
- }
-
return null
}
}
@@ -435,34 +425,3 @@ class FCMConfigModel(parentModel: Model, parentProperty: String) : Model(parentM
setOptStringProperty(::apiKey.name, value)
}
}
-
-/**
- * Configuration related to OneSignal's remote logging.
- */
-class RemoteLoggingConfigModel(
- parentModel: Model,
- parentProperty: String,
-) : Model(parentModel, parentProperty) {
- /**
- * The minimum log level to send to OneSignal's server.
- * If null, defaults to ERROR level for client-side logging.
- * If NONE, no logs (including errors) will be sent remotely.
- *
- * Log levels: NONE < FATAL < ERROR < WARN < INFO < DEBUG < VERBOSE
- */
- var logLevel: com.onesignal.debug.LogLevel?
- get() = getOptEnumProperty(::logLevel.name)
- set(value) {
- setOptEnumProperty(::logLevel.name, value)
- }
-
- /**
- * Whether remote logging is enabled.
- * Set by backend config hydration — true when the server sends a valid log_level, false otherwise.
- */
- var isEnabled: Boolean
- get() = getBooleanProperty(::isEnabled.name) { false }
- set(value) {
- setBooleanProperty(::isEnabled.name, value)
- }
-}
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/ConfigModelStore.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/ConfigModelStore.kt
index 801a85e903..687a8547b0 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/ConfigModelStore.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/ConfigModelStore.kt
@@ -3,8 +3,7 @@ package com.onesignal.core.internal.config
import com.onesignal.common.modeling.SimpleModelStore
import com.onesignal.common.modeling.SingletonModelStore
import com.onesignal.core.internal.preferences.IPreferencesService
-const val CONFIG_NAME_SPACE = "config"
open class ConfigModelStore(prefs: IPreferencesService) : SingletonModelStore(
- SimpleModelStore({ ConfigModel() }, CONFIG_NAME_SPACE, prefs),
+ SimpleModelStore({ ConfigModel() }, "config", prefs),
)
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/impl/ConfigModelStoreListener.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/impl/ConfigModelStoreListener.kt
index 581943bc58..87d7eae6b0 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/impl/ConfigModelStoreListener.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/impl/ConfigModelStoreListener.kt
@@ -103,9 +103,6 @@ internal class ConfigModelStoreListener(
params.influenceParams.isIndirectEnabled?.let { config.influenceParams.isIndirectEnabled = it }
params.influenceParams.isUnattributedEnabled?.let { config.influenceParams.isUnattributedEnabled = it }
- params.remoteLoggingParams.logLevel?.let { config.remoteLoggingParams.logLevel = it }
- config.remoteLoggingParams.isEnabled = params.remoteLoggingParams.isEnabled
-
_configModelStore.replace(config, ModelChangeTags.HYDRATE)
success = true
} catch (ex: BackendException) {
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/http/OneSignalService.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/http/OneSignalService.kt
deleted file mode 100644
index b7533961de..0000000000
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/http/OneSignalService.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.onesignal.core.internal.http
-
-/** Central API base URL used by all SDK HTTP traffic, including Otel log export. */
-object OneSignalService {
-// const val ONESIGNAL_API_BASE_URL = "https://api.staging.onesignal.com/"
- const val ONESIGNAL_API_BASE_URL = "https://api.onesignal.com/"
-}
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/http/impl/HttpClient.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/http/impl/HttpClient.kt
index 747b0b7085..00748d428e 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/http/impl/HttpClient.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/http/impl/HttpClient.kt
@@ -29,9 +29,6 @@ import java.net.UnknownHostException
import java.util.Scanner
import javax.net.ssl.HttpsURLConnection
-internal const val HTTP_SDK_VERSION_HEADER_KEY = "SDK-Version"
-internal val HTTP_SDK_VERSION_HEADER_VALUE = "onesignal/android/${OneSignalUtils.sdkVersion}"
-
internal class HttpClient(
private val _connectionFactory: IHttpConnectionFactory,
private val _prefs: IPreferencesService,
@@ -96,7 +93,7 @@ internal class HttpClient(
return@withTimeout makeRequestIODispatcher(url, method, jsonBody, timeout, headers)
}
} catch (e: TimeoutCancellationException) {
- Logging.info("HttpClient: Request timed out: $url", e)
+ Logging.error("HttpClient: Request timed out: $url", e)
return HttpResponse(0, null, e)
} catch (e: Throwable) {
return HttpResponse(0, null, e)
@@ -138,7 +135,7 @@ internal class HttpClient(
con.useCaches = false
con.connectTimeout = timeout
con.readTimeout = timeout
- con.setRequestProperty(HTTP_SDK_VERSION_HEADER_KEY, HTTP_SDK_VERSION_HEADER_VALUE)
+ con.setRequestProperty("SDK-Version", "onesignal/android/" + OneSignalUtils.sdkVersion)
if (OneSignalWrapper.sdkType != null && OneSignalWrapper.sdkVersion != null) {
con.setRequestProperty("SDK-Wrapper", "onesignal/${OneSignalWrapper.sdkType}/${OneSignalWrapper.sdkVersion}")
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/impl/OperationRepo.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/impl/OperationRepo.kt
index 78983cc7fc..1861261506 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/impl/OperationRepo.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/impl/OperationRepo.kt
@@ -264,7 +264,7 @@ internal class OperationRepo(
ExecutionResult.FAIL_NORETRY,
ExecutionResult.FAIL_CONFLICT,
-> {
- Logging.warn("Operation execution failed without retry: $operations")
+ Logging.error("Operation execution failed without retry: $operations")
// on failure we remove the operation from the store and wake any waiters
ops.forEach { _operationModelStore.remove(it.operation.id) }
ops.forEach { it.waiter?.wake(false) }
@@ -279,7 +279,7 @@ internal class OperationRepo(
}
}
ExecutionResult.FAIL_RETRY -> {
- Logging.info("Operation execution failed, retrying: $operations")
+ Logging.error("Operation execution failed, retrying: $operations")
// add back all operations to the front of the queue to be re-executed.
synchronized(queue) {
ops.reversed().forEach {
@@ -341,7 +341,7 @@ internal class OperationRepo(
val delayForOnRetries = retries * _configModelStore.model.opRepoDefaultFailRetryBackoff
val delayFor = max(delayForOnRetries, retryAfterSecondsNonNull * 1_000)
if (delayFor < 1) return
- Logging.debug("Operations being delay for: $delayFor ms")
+ Logging.error("Operations being delay for: $delayFor ms")
withTimeoutOrNull(delayFor) {
retryWaiter.waitForWake()
}
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/time/ITime.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/time/ITime.kt
index 8f1824d481..ff35096efd 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/time/ITime.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/time/ITime.kt
@@ -10,9 +10,4 @@ interface ITime {
* current time and midnight, January 1, 1970 UTC).
*/
val currentTimeMillis: Long
-
- /**
- * Returns how long the app has been running.
- */
- val processUptimeMillis: Long
}
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/time/impl/Time.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/time/impl/Time.kt
index 753ef124d5..231f37edf3 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/time/impl/Time.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/time/impl/Time.kt
@@ -1,14 +1,8 @@
package com.onesignal.core.internal.time.impl
-import android.os.Build
-import android.os.SystemClock
-import androidx.annotation.RequiresApi
import com.onesignal.core.internal.time.ITime
internal class Time : ITime {
override val currentTimeMillis: Long
get() = System.currentTimeMillis()
- override val processUptimeMillis: Long
- @RequiresApi(Build.VERSION_CODES.N)
- get() = SystemClock.uptimeMillis() - android.os.Process.getStartUptimeMillis()
}
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/debug/LogLevel.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/debug/LogLevel.kt
index e88922909c..9c3f99e877 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/debug/LogLevel.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/debug/LogLevel.kt
@@ -49,19 +49,5 @@ enum class LogLevel {
fun fromInt(value: Int): LogLevel {
return values()[value]
}
-
- /**
- * Parses a [LogLevel] from its string name (case-insensitive).
- * Returns `null` if the string is null or not a valid level name.
- */
- @JvmStatic
- fun fromString(value: String?): LogLevel? {
- if (value == null) return null
- return try {
- valueOf(value.uppercase())
- } catch (_: IllegalArgumentException) {
- null
- }
- }
}
}
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/debug/internal/crash/AnrConstants.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/debug/internal/crash/AnrConstants.kt
deleted file mode 100644
index 3f0e115eb2..0000000000
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/debug/internal/crash/AnrConstants.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.onesignal.debug.internal.crash
-
-/**
- * Constants for ANR (Application Not Responding) detection configuration.
- */
-internal object AnrConstants {
- /**
- * Default ANR threshold in milliseconds.
- * Android's default ANR threshold is 5 seconds (5000ms).
- * An ANR is reported when the main thread is unresponsive for this duration.
- */
- const val DEFAULT_ANR_THRESHOLD_MS: Long = 5_000L
-
- /**
- * Default check interval in milliseconds.
- * The ANR detector checks the main thread responsiveness every 2 seconds.
- */
- const val DEFAULT_CHECK_INTERVAL_MS: Long = 2_000L
-}
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/debug/internal/crash/OneSignalCrashHandlerFactory.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/debug/internal/crash/OneSignalCrashHandlerFactory.kt
deleted file mode 100644
index 568134287f..0000000000
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/debug/internal/crash/OneSignalCrashHandlerFactory.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.onesignal.debug.internal.crash
-
-import android.content.Context
-import com.onesignal.debug.internal.logging.Logging
-import com.onesignal.debug.internal.logging.otel.android.createAndroidOtelPlatformProvider
-import com.onesignal.otel.IOtelCrashHandler
-import com.onesignal.otel.IOtelLogger
-import com.onesignal.otel.OtelFactory
-
-/**
- * Factory for creating Otel-based crash handlers.
- * Callers must verify [OtelSdkSupport.isSupported] before calling [createCrashHandler].
- *
- * Uses minimal dependencies - only Context and logger.
- * Platform provider uses OtelIdResolver internally which reads from SharedPreferences.
- */
-internal object OneSignalCrashHandlerFactory {
- /**
- * Creates an Otel crash handler. Must only be called on supported devices
- * (SDK >= [OtelSdkSupport.MIN_SDK_VERSION]).
- *
- * @param context Android context for creating platform provider
- * @param logger Logger instance (can be shared with other components)
- * @throws IllegalArgumentException if called on an unsupported SDK
- */
- fun createCrashHandler(
- context: Context,
- logger: IOtelLogger,
- ): IOtelCrashHandler {
- require(OtelSdkSupport.isSupported) {
- "createCrashHandler called on unsupported SDK (< ${OtelSdkSupport.MIN_SDK_VERSION})"
- }
-
- Logging.info("OneSignal: Creating Otel crash handler (SDK >= ${OtelSdkSupport.MIN_SDK_VERSION})")
- val platformProvider = createAndroidOtelPlatformProvider(context)
- return OtelFactory.createCrashHandler(platformProvider, logger)
- }
-}
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/debug/internal/crash/OneSignalCrashUploaderWrapper.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/debug/internal/crash/OneSignalCrashUploaderWrapper.kt
deleted file mode 100644
index 2f9f7c9c3a..0000000000
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/debug/internal/crash/OneSignalCrashUploaderWrapper.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-package com.onesignal.debug.internal.crash
-
-import com.onesignal.common.threading.suspendifyOnThread
-import com.onesignal.core.internal.application.IApplicationService
-import com.onesignal.core.internal.startup.IStartableService
-import com.onesignal.debug.internal.logging.otel.android.AndroidOtelLogger
-import com.onesignal.debug.internal.logging.otel.android.createAndroidOtelPlatformProvider
-import com.onesignal.otel.OtelFactory
-import com.onesignal.otel.crash.OtelCrashUploader
-
-/**
- * Android-specific wrapper for OtelCrashUploader that implements IStartableService.
- *
- * This is a thin adapter layer that:
- * 1. Takes Android-specific services as dependencies
- * 2. Creates platform-agnostic implementations (IOtelPlatformProvider, IOtelLogger)
- * 3. Wraps the platform-agnostic OtelCrashUploader for Android service architecture
- *
- * The OtelCrashUploader itself is fully platform-agnostic and can be used directly
- * in KMP projects by providing platform-specific implementations of:
- * - IOtelPlatformProvider (inject all platform values)
- * - IOtelLogger (platform logging interface)
- *
- * Example KMP usage:
- * ```kotlin
- * val platformProvider = MyPlatformProvider(...) // iOS/Android specific
- * val logger = MyPlatformLogger() // iOS/Android specific
- * val uploader = OtelFactory.createCrashUploader(platformProvider, logger)
- * // Use uploader.start() in a coroutine
- * ```
- */
-internal class OneSignalCrashUploaderWrapper(
- private val applicationService: IApplicationService,
-) : IStartableService {
- private val uploader: OtelCrashUploader by lazy {
- // Create Android-specific platform provider (injects Android values)
- val platformProvider = createAndroidOtelPlatformProvider(
- applicationService.appContext
- )
- // Create Android-specific logger (delegates to Android Logging)
- val logger = AndroidOtelLogger()
- // Create platform-agnostic uploader using factory
- OtelFactory.createCrashUploader(platformProvider, logger)
- }
-
- @Suppress("TooGenericExceptionCaught")
- override fun start() {
- if (!OtelSdkSupport.isSupported) return
- suspendifyOnThread {
- try {
- uploader.start()
- } catch (t: Throwable) {
- com.onesignal.debug.internal.logging.Logging.warn(
- "OneSignal: Crash uploader failed to start: ${t.message}",
- t,
- )
- }
- }
- }
-}
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/debug/internal/crash/OtelAnrDetector.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/debug/internal/crash/OtelAnrDetector.kt
deleted file mode 100644
index d7ad6960a1..0000000000
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/debug/internal/crash/OtelAnrDetector.kt
+++ /dev/null
@@ -1,221 +0,0 @@
-package com.onesignal.debug.internal.crash
-
-import android.os.Handler
-import android.os.Looper
-import com.onesignal.otel.IOtelCrashReporter
-import com.onesignal.otel.IOtelLogger
-import com.onesignal.otel.IOtelOpenTelemetryCrash
-import com.onesignal.otel.OtelFactory
-import com.onesignal.otel.crash.IOtelAnrDetector
-import kotlinx.coroutines.runBlocking
-import java.util.concurrent.atomic.AtomicBoolean
-import java.util.concurrent.atomic.AtomicLong
-
-/**
- * Android-specific implementation of ANR detection.
- *
- * Uses a watchdog pattern to monitor the main thread:
- * - Posts a message to the main thread every check interval
- * - If the main thread doesn't respond within the ANR threshold, reports an ANR
- * - Captures the main thread's stack trace when ANR is detected
- *
- * This is a standalone component that can be initialized independently of the crash handler.
- * It creates its own crash reporter to save ANR reports.
- */
-internal class OtelAnrDetector(
- openTelemetryCrash: IOtelOpenTelemetryCrash,
- private val logger: IOtelLogger,
- private val anrThresholdMs: Long = AnrConstants.DEFAULT_ANR_THRESHOLD_MS,
- private val checkIntervalMs: Long = AnrConstants.DEFAULT_CHECK_INTERVAL_MS,
-) : IOtelAnrDetector {
- private val crashReporter: IOtelCrashReporter = OtelFactory.createCrashReporter(openTelemetryCrash, logger)
- private val mainHandler = Handler(Looper.getMainLooper())
- private val isMonitoring = AtomicBoolean(false)
- private val lastResponseTime = AtomicLong(System.currentTimeMillis())
- private val lastAnrReportTime = AtomicLong(0L)
- private var watchdogThread: Thread? = null
- private var watchdogRunnable: Runnable? = null
- private var mainThreadRunnable: Runnable? = null
-
- companion object {
- private const val TAG = "OtelAnrDetector"
-
- // Minimum time between ANR reports (to avoid duplicate reports for the same ANR)
- private const val MIN_TIME_BETWEEN_ANR_REPORTS_MS = 30_000L // 30 seconds
- }
-
- override fun start() {
- if (isMonitoring.getAndSet(true)) {
- logger.warn("$TAG: Already monitoring for ANRs, skipping start")
- return
- }
-
- logger.info("$TAG: Starting ANR detection (threshold: ${anrThresholdMs}ms, check interval: ${checkIntervalMs}ms)")
-
- setupRunnables()
- startWatchdogThread()
-
- logger.info("$TAG: ✅ ANR detection started successfully")
- }
-
- @Suppress("TooGenericExceptionCaught")
- private fun setupRunnables() {
- // Runnable that runs on the main thread to indicate it's responsive
- mainThreadRunnable = Runnable {
- lastResponseTime.set(System.currentTimeMillis())
- }
-
- // Runnable that runs on the watchdog thread to check for ANRs
- watchdogRunnable = Runnable {
- while (isMonitoring.get()) {
- try {
- checkForAnr()
- } catch (e: InterruptedException) {
- // Thread was interrupted, stop monitoring
- logger.info("$TAG: Watchdog thread interrupted, stopping ANR detection")
- break
- } catch (t: Throwable) {
- logger.error("$TAG: Error in ANR watchdog: ${t.message} - ${t.javaClass.simpleName}")
- }
- }
- }
- }
-
- private fun checkForAnr() {
- val runnable = mainThreadRunnable ?: return
- mainHandler.post(runnable)
-
- // Wait for the check interval
- Thread.sleep(checkIntervalMs)
-
- // Check if main thread responded
- val timeSinceLastResponse = System.currentTimeMillis() - lastResponseTime.get()
- if (timeSinceLastResponse > anrThresholdMs) {
- handleAnrDetected(timeSinceLastResponse)
- } else {
- handleMainThreadResponsive()
- }
- }
-
- private fun handleAnrDetected(timeSinceLastResponse: Long) {
- // Main thread hasn't responded - ANR detected!
- val now = System.currentTimeMillis()
- val timeSinceLastReport = now - lastAnrReportTime.get()
-
- // Only report if enough time has passed since last report (avoid duplicates)
- if (timeSinceLastReport > MIN_TIME_BETWEEN_ANR_REPORTS_MS) {
- logger.warn("$TAG: ⚠️ ANR detected! Main thread unresponsive for ${timeSinceLastResponse}ms")
- lastAnrReportTime.set(now)
- reportAnr(timeSinceLastResponse)
- } else {
- logger.debug("$TAG: ANR still ongoing (${timeSinceLastResponse}ms), but already reported recently (${timeSinceLastReport}ms ago)")
- }
- }
-
- private fun handleMainThreadResponsive() {
- // Main thread is responsive - reset ANR report time so we can detect new ANRs
- if (lastAnrReportTime.get() > 0) {
- lastAnrReportTime.set(0L)
- logger.debug("$TAG: Main thread recovered, ready to detect new ANRs")
- }
- }
-
- private fun startWatchdogThread() {
- // Start the watchdog thread
- watchdogThread = Thread(watchdogRunnable, "OneSignal-ANR-Watchdog")
- watchdogThread?.isDaemon = true
- watchdogThread?.start()
- }
-
- override fun stop() {
- if (!isMonitoring.getAndSet(false)) {
- logger.warn("$TAG: Not monitoring, skipping stop")
- return
- }
-
- logger.info("$TAG: Stopping ANR detection...")
-
- // Interrupt the watchdog thread to stop it
- watchdogThread?.interrupt()
- watchdogThread = null
- watchdogRunnable = null
- // Remove pending callbacks before nulling to prevent execution after stop
- mainThreadRunnable?.let { mainHandler.removeCallbacks(it) }
- mainThreadRunnable = null
-
- logger.info("$TAG: ✅ ANR detection stopped")
- }
-
- @Suppress("TooGenericExceptionCaught")
- private fun reportAnr(unresponsiveDurationMs: Long) {
- try {
- logger.info("$TAG: Checking if ANR is OneSignal-related (unresponsive for ${unresponsiveDurationMs}ms)")
-
- // Get the main thread's stack trace
- val mainThread = Looper.getMainLooper().thread
- val stackTrace = mainThread.stackTrace
-
- // Only report if OneSignal is at fault (uses centralized utility from otel module)
- val isOneSignalAtFault = com.onesignal.otel.crash.isOneSignalAtFault(stackTrace)
-
- if (!isOneSignalAtFault) {
- logger.debug("$TAG: ANR is not OneSignal-related, skipping report")
- return
- }
-
- logger.info("$TAG: OneSignal-related ANR detected, reporting...")
-
- // Create an ANR exception with the stack trace
- val anrException = ApplicationNotRespondingException(
- "Application Not Responding: Main thread blocked for ${unresponsiveDurationMs}ms",
- stackTrace
- )
-
- // Report it as a crash (but mark it as ANR)
- runBlocking {
- crashReporter.saveCrash(mainThread, anrException)
- }
-
- logger.info("$TAG: ✅ ANR report saved successfully")
- } catch (t: Throwable) {
- logger.error("$TAG: Failed to report ANR: ${t.message} - ${t.javaClass.simpleName}")
- }
- }
-
- /**
- * Custom exception type for ANRs.
- * This allows us to distinguish ANRs from regular crashes in the crash reporting system.
- */
- private class ApplicationNotRespondingException(
- message: String,
- stackTrace: Array
- ) : RuntimeException(message) {
- init {
- this.stackTrace = stackTrace
- }
- }
-}
-
-// Use the centralized isOneSignalAtFault from otel module
-
-/**
- * Factory function to create an ANR detector for Android.
- * This is in the core module since it needs to access Android-specific classes.
- */
-
-internal fun createAnrDetector(
- platformProvider: com.onesignal.otel.IOtelPlatformProvider,
- logger: IOtelLogger,
- anrThresholdMs: Long = AnrConstants.DEFAULT_ANR_THRESHOLD_MS,
- checkIntervalMs: Long = AnrConstants.DEFAULT_CHECK_INTERVAL_MS,
-): IOtelAnrDetector {
- // Use the factory to create crash local instance (keeps implementation details internal)
- val crashLocal = OtelFactory.createCrashLocalTelemetry(platformProvider)
-
- return OtelAnrDetector(
- crashLocal,
- logger,
- anrThresholdMs,
- checkIntervalMs
- )
-}
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/debug/internal/crash/OtelSdkSupport.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/debug/internal/crash/OtelSdkSupport.kt
deleted file mode 100644
index 47fc0034de..0000000000
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/debug/internal/crash/OtelSdkSupport.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.onesignal.debug.internal.crash
-
-import android.os.Build
-
-/**
- * Centralizes the SDK version requirement for Otel-based features
- * (crash reporting, ANR detection, remote log shipping).
- *
- * [isSupported] is writable internally so that unit tests can override
- * the device-level gate without Robolectric @Config gymnastics.
- */
-internal object OtelSdkSupport {
- /** Otel libraries require Android O (API 26) or above. */
- const val MIN_SDK_VERSION = Build.VERSION_CODES.O // 26
-
- /**
- * Whether the current device meets the minimum SDK requirement.
- * Production code should treat this as read-only; tests may flip it via [reset]/direct set.
- */
- var isSupported: Boolean = Build.VERSION.SDK_INT >= MIN_SDK_VERSION
- internal set
-
- /** Restores the runtime-detected value — call in test teardown. */
- fun reset() {
- isSupported = Build.VERSION.SDK_INT >= MIN_SDK_VERSION
- }
-}
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/debug/internal/logging/Logging.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/debug/internal/logging/Logging.kt
index 673db1b8da..a4db03407a 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/debug/internal/logging/Logging.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/debug/internal/logging/Logging.kt
@@ -6,12 +6,6 @@ import com.onesignal.core.internal.application.IApplicationService
import com.onesignal.debug.ILogListener
import com.onesignal.debug.LogLevel
import com.onesignal.debug.OneSignalLogEvent
-import com.onesignal.otel.IOtelOpenTelemetryRemote
-import com.onesignal.otel.OtelLoggingHelper
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.launch
import java.io.PrintWriter
import java.io.StringWriter
import java.util.concurrent.CopyOnWriteArraySet
@@ -23,38 +17,6 @@ object Logging {
private val logListeners = CopyOnWriteArraySet()
- /**
- * Optional Otel remote telemetry for logging SDK events.
- * Set this when remote logging is enabled.
- */
- @Volatile
- private var otelRemoteTelemetry: IOtelOpenTelemetryRemote? = null
-
- /**
- * Function to check if a specific log level should be sent remotely.
- * Set this to dynamically check remote logging configuration based on log level.
- */
- @Volatile
- private var shouldSendLogLevel: (LogLevel) -> Boolean = { false }
-
- /**
- * Sets the Otel remote telemetry instance and log level check function.
- * This should be called when remote logging is enabled.
- *
- * @param telemetry The Otel remote telemetry instance
- * @param shouldSend Function that returns true if a log level should be sent remotely
- */
- fun setOtelTelemetry(
- telemetry: IOtelOpenTelemetryRemote?,
- shouldSend: (LogLevel) -> Boolean = { false },
- ) {
- otelRemoteTelemetry = telemetry
- shouldSendLogLevel = shouldSend
- }
-
- // Coroutine scope for async Otel logging (non-blocking)
- private val otelLoggingScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
-
@JvmStatic
var logLevel = LogLevel.WARN
@@ -131,7 +93,6 @@ object Logging {
logToLogcat(level, fullMessage, throwable)
showVisualLogging(level, fullMessage, throwable)
callLogListeners(level, fullMessage, throwable)
- logToOtel(level, fullMessage, throwable)
}
private fun logToLogcat(
@@ -199,42 +160,6 @@ object Logging {
}
}
- /**
- * Logs to Otel remote telemetry if enabled.
- * This is non-blocking and runs asynchronously.
- */
- @Suppress("TooGenericExceptionCaught", "ReturnCount")
- private fun logToOtel(
- level: LogLevel,
- message: String,
- throwable: Throwable?,
- ) {
- val telemetry = otelRemoteTelemetry ?: return
-
- // Skip NONE level
- if (level == LogLevel.NONE) return
-
- // Check if this log level should be sent remotely
- if (!shouldSendLogLevel(level)) return
-
- // Log asynchronously (non-blocking)
- otelLoggingScope.launch {
- try {
- OtelLoggingHelper.logToOtel(
- telemetry = telemetry,
- level = level.name,
- message = message,
- exceptionType = throwable?.javaClass?.name,
- exceptionMessage = throwable?.message,
- exceptionStacktrace = throwable?.stackTraceToString(),
- )
- } catch (t: Throwable) {
- // Don't log Otel errors to Otel (would cause infinite loop)
- android.util.Log.e(TAG, "Failed to log to Otel: ${t.message}", t)
- }
- }
- }
-
fun addListener(listener: ILogListener) {
logListeners.add(listener)
}
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/debug/internal/logging/otel/android/AndroidOtelLogger.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/debug/internal/logging/otel/android/AndroidOtelLogger.kt
deleted file mode 100644
index 0452a8dca3..0000000000
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/debug/internal/logging/otel/android/AndroidOtelLogger.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.onesignal.debug.internal.logging.otel.android
-
-import com.onesignal.debug.internal.logging.Logging
-import com.onesignal.otel.IOtelLogger
-
-/**
- * Android-specific implementation of IOtelLogger.
- * Delegates to the existing Logging object.
- */
-internal class AndroidOtelLogger : IOtelLogger {
- override fun error(message: String) {
- Logging.error(message)
- }
-
- override fun warn(message: String) {
- Logging.warn(message)
- }
-
- override fun info(message: String) {
- Logging.info(message)
- }
-
- override fun debug(message: String) {
- Logging.debug(message)
- }
-}
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/debug/internal/logging/otel/android/OtelIdResolver.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/debug/internal/logging/otel/android/OtelIdResolver.kt
deleted file mode 100644
index b205fffd9f..0000000000
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/debug/internal/logging/otel/android/OtelIdResolver.kt
+++ /dev/null
@@ -1,247 +0,0 @@
-package com.onesignal.debug.internal.logging.otel.android
-
-import android.content.Context
-import com.onesignal.common.IDManager
-import com.onesignal.core.internal.config.ConfigModel
-import com.onesignal.core.internal.preferences.PreferenceOneSignalKeys
-import com.onesignal.core.internal.preferences.PreferenceStores
-import com.onesignal.debug.internal.logging.Logging
-import com.onesignal.user.internal.backend.IdentityConstants
-import org.json.JSONArray
-import org.json.JSONObject
-
-/**
- * Resolves OneSignal IDs from SharedPreferences with fallback strategies.
- * This class encapsulates all the logic for reading IDs from ConfigModelStore and legacy SharedPreferences,
- * making it easier to maintain and test.
- *
- * Note: Data is read fresh from SharedPreferences each time (not cached) to ensure test reliability
- * and correctness. The performance impact is minimal since these methods are not called frequently.
- */
-@Suppress("TooManyFunctions") // This class intentionally groups related ID resolution functions
-internal class OtelIdResolver(
- private val context: Context?,
-) {
- companion object {
- /**
- * Hardcoded error appId prefix when appId cannot be resolved.
- */
- private const val ERROR_APP_ID_RESOLVE = "00000000-0000-4000-a000-000000000000"
- private const val ERROR_APP_ID_PREFIX_UNKNOWN = "e1100000-0000-4000-a000-000000000000"
- private const val ERROR_APP_ID_PREFIX_NO_APPID_IN_CONFIG = "e1100000-0000-4000-a000-000000000001"
- private const val ERROR_APP_ID_PREFIX_NO_CONFIG_STORE = "e1100000-0000-4000-a000-000000000002"
- private const val ERROR_APP_ID_PREFIX_NO_APPID_IN_CONFIG_STORE = "e1100000-0000-4000-a000-000000000003"
- private const val ERROR_APP_ID_PREFIX_NO_CONTEXT = "e1100000-0000-4000-a000-000000000004"
- }
-
- // Get SharedPreferences instance (fresh each time to avoid caching issues in tests)
- private fun getSharedPreferences(): android.content.SharedPreferences? {
- return context?.getSharedPreferences(PreferenceStores.ONESIGNAL, Context.MODE_PRIVATE)
- }
-
- // Read ConfigModelStore JSON (fresh read each time for testability)
- // In production, this is called multiple times per resolver instance, but the performance impact is minimal
- // and this ensures test reliability
- @Suppress("TooGenericExceptionCaught", "SwallowedException")
- private fun readConfigModel(): JSONObject? {
- return try {
- val configStoreJson = getSharedPreferences()?.getString(
- PreferenceOneSignalKeys.MODEL_STORE_PREFIX + com.onesignal.core.internal.config.CONFIG_NAME_SPACE,
- null
- )
-
- if (configStoreJson != null && configStoreJson.isNotEmpty()) {
- val jsonArray = JSONArray(configStoreJson)
- if (jsonArray.length() > 0) {
- jsonArray.getJSONObject(0)
- } else {
- null
- }
- } else {
- null
- }
- } catch (e: Exception) {
- null
- }
- }
-
- // Check if ConfigModelStore exists but is empty (to distinguish from "not found")
- @Suppress("TooGenericExceptionCaught", "SwallowedException")
- private fun hasEmptyConfigStore(): Boolean {
- return try {
- val configStoreJson = getSharedPreferences()?.getString(
- PreferenceOneSignalKeys.MODEL_STORE_PREFIX + com.onesignal.core.internal.config.CONFIG_NAME_SPACE,
- null
- )
- if (configStoreJson != null && configStoreJson.isNotEmpty()) {
- val jsonArray = JSONArray(configStoreJson)
- jsonArray.length() == 0
- } else {
- false
- }
- } catch (e: Exception) {
- false
- }
- }
-
- /**
- * Resolves appId with the following fallback chain:
- * 1. Try ConfigModelStore in SharedPreferences (MODEL_STORE_config)
- * 2. Try legacy OneSignal SharedPreferences
- * 3. Return error appId with affix if all fail
- */
- @Suppress("TooGenericExceptionCaught")
- fun resolveAppId(): String {
- return try {
- val configModel = readConfigModel()
- val appIdFromConfig = extractAppIdFromConfig(configModel)
- appIdFromConfig ?: resolveAppIdFromLegacy(configModel)
- } catch (e: Exception) {
- Logging.error("Trying resolve the app Id${e.message}")
- ERROR_APP_ID_RESOLVE
- }
- }
-
- private fun extractAppIdFromConfig(configModel: JSONObject?): String? {
- if (configModel == null) return null
- val appIdProperty = ConfigModel::appId
- return if (configModel.has(appIdProperty.name)) {
- val appId = configModel.getString(appIdProperty.name)
- appId.ifEmpty { null }
- } else {
- null
- }
- }
-
- @Suppress("TooGenericExceptionCaught", "SwallowedException", "NestedBlockDepth")
- private fun resolveAppIdFromLegacy(configModel: JSONObject?): String {
- // Second: fall back to legacy OneSignal SharedPreferences
- val legacyAppId = try {
- getSharedPreferences()?.getString(PreferenceOneSignalKeys.PREFS_LEGACY_APP_ID, null)
- ?.takeIf { it.isNotEmpty() }
- } catch (e: Exception) {
- null
- }
-
- return legacyAppId ?: run {
- // Third: return error appId with affix
- return when {
- context == null -> ERROR_APP_ID_PREFIX_NO_CONTEXT
- hasEmptyConfigStore() -> ERROR_APP_ID_PREFIX_NO_APPID_IN_CONFIG_STORE // Store exists but is empty array
- configModel == null -> ERROR_APP_ID_PREFIX_NO_CONFIG_STORE // Store doesn't exist
- !configModel.has("appId") -> ERROR_APP_ID_PREFIX_NO_APPID_IN_CONFIG // Store exists but no appId field
- else -> ERROR_APP_ID_PREFIX_UNKNOWN
- }
- }
- }
-
- /**
- * Resolves onesignalId with the following fallback chain:
- * 1. Try IdentityModelStore in SharedPreferences (MODEL_STORE_identity)
- * 2. Return null if all fail
- */
- @Suppress("TooGenericExceptionCaught", "SwallowedException", "NestedBlockDepth")
- fun resolveOnesignalId(): String? {
- return try {
- val identityStoreJson = getSharedPreferences()?.getString(
- PreferenceOneSignalKeys.MODEL_STORE_PREFIX + com.onesignal.user.internal.identity.IDENTITY_NAME_SPACE,
- null
- )
-
- if (identityStoreJson != null && identityStoreJson.isNotEmpty()) {
- extractOnesignalIdFromJson(identityStoreJson)
- } else {
- null
- }
- } catch (e: Exception) {
- null
- }
- }
-
- private fun extractOnesignalIdFromJson(identityStoreJson: String): String? {
- val jsonArray = JSONArray(identityStoreJson)
- if (jsonArray.length() > 0) {
- val identityModel = jsonArray.getJSONObject(0)
- if (identityModel.has(IdentityConstants.ONESIGNAL_ID)) {
- val onesignalId = identityModel.getString(IdentityConstants.ONESIGNAL_ID)
- return onesignalId.takeIf { it.isNotEmpty() && !IDManager.isLocalId(it) }
- }
- }
- return null
- }
-
- /**
- * Resolves pushSubscriptionId from cached ConfigModelStore.
- * Returns null if not found or if it's a local ID.
- */
- @Suppress("TooGenericExceptionCaught", "SwallowedException")
- fun resolvePushSubscriptionId(): String? {
- return try {
- val configModel = readConfigModel()
- val pushSubscriptionIdProperty = ConfigModel::pushSubscriptionId
- if (configModel != null && configModel.has(pushSubscriptionIdProperty.name)) {
- val pushSubscriptionId = configModel.getString(pushSubscriptionIdProperty.name)
- pushSubscriptionId.takeIf { it.isNotEmpty() && !IDManager.isLocalId(pushSubscriptionId) }
- } else {
- null
- }
- } catch (e: Exception) {
- null
- }
- }
-
- /**
- * Resolves whether remote logging is enabled from cached ConfigModelStore.
- * Enabled is derived from the presence of a valid logLevel:
- * - "logging_config": {} → no logLevel → disabled (not on allowlist)
- * - "logging_config": {"log_level": "ERROR"} → has logLevel → enabled (on allowlist)
- * Returns false if not found, empty, or on error (disabled by default on first launch).
- */
- @Suppress("TooGenericExceptionCaught", "SwallowedException")
- fun resolveRemoteLoggingEnabled(): Boolean {
- return try {
- val logLevel = resolveRemoteLogLevel()
- logLevel != null && logLevel != com.onesignal.debug.LogLevel.NONE
- } catch (e: Exception) {
- false
- }
- }
-
- /**
- * Resolves remote log level from cached ConfigModelStore.
- * Returns null if not found or if there's an error.
- */
- @Suppress("TooGenericExceptionCaught", "SwallowedException", "NestedBlockDepth")
- fun resolveRemoteLogLevel(): com.onesignal.debug.LogLevel? {
- return try {
- val configModel = readConfigModel()
- val remoteLoggingParamsProperty = ConfigModel::remoteLoggingParams
- if (configModel != null && configModel.has(remoteLoggingParamsProperty.name)) {
- extractLogLevelFromParams(configModel.getJSONObject(remoteLoggingParamsProperty.name))
- } else {
- null
- }
- } catch (e: Exception) {
- null
- }
- }
-
- private fun extractLogLevelFromParams(remoteLoggingParams: JSONObject): com.onesignal.debug.LogLevel? =
- com.onesignal.debug.LogLevel.fromString(
- if (remoteLoggingParams.has("logLevel")) remoteLoggingParams.getString("logLevel") else null
- )
-
- /**
- * Resolves install ID from SharedPreferences.
- * Returns "InstallId-Null" if not found, "InstallId-NotFound" if there's an error.
- */
- @Suppress("TooGenericExceptionCaught", "SwallowedException")
- fun resolveInstallId(): String {
- return try {
- val installIdString = getSharedPreferences()?.getString(PreferenceOneSignalKeys.PREFS_OS_INSTALL_ID, "InstallId-Null")
- installIdString ?: "InstallId-Null"
- } catch (e: Exception) {
- "InstallId-NotFound"
- }
- }
-}
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/debug/internal/logging/otel/android/OtelPlatformProvider.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/debug/internal/logging/otel/android/OtelPlatformProvider.kt
deleted file mode 100644
index eebf9469c0..0000000000
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/debug/internal/logging/otel/android/OtelPlatformProvider.kt
+++ /dev/null
@@ -1,164 +0,0 @@
-package com.onesignal.debug.internal.logging.otel.android
-
-import android.app.ActivityManager
-import android.content.Context
-import android.os.Build
-import com.onesignal.common.OneSignalUtils
-import com.onesignal.common.OneSignalWrapper
-import com.onesignal.core.internal.http.OneSignalService
-import com.onesignal.debug.internal.logging.Logging
-import com.onesignal.otel.IOtelPlatformProvider
-
-/**
- * Configuration for AndroidOtelPlatformProvider.
- */
-internal data class OtelPlatformProviderConfig(
- val crashStoragePath: String,
- val appPackageId: String,
- val appVersion: String,
- val context: Context? = null,
- val getIsInForeground: (() -> Boolean?)? = null,
-)
-
-/**
- * Android-specific implementation of IOtelPlatformProvider.
- * Reads all values directly from SharedPreferences and system services.
- * No SDK service dependencies required.
- *
- * All IDs (appId, onesignalId, pushSubscriptionId) are resolved from SharedPreferences via OtelIdResolver.
- * Remote log level defaults to ERROR if not found in config.
- */
-internal class OtelPlatformProvider(
- config: OtelPlatformProviderConfig,
-) : IOtelPlatformProvider {
- override val appPackageId: String = config.appPackageId
- override val appVersion: String = config.appVersion
- private val context: Context? = config.context
- private val getIsInForeground: (() -> Boolean?)? = config.getIsInForeground
- private val idResolver = OtelIdResolver(context)
-
- // Top-level attributes (static, calculated once)
- override suspend fun getInstallId(): String = idResolver.resolveInstallId()
-
- override val sdkBase: String = "android"
-
- override val sdkBaseVersion: String = OneSignalUtils.sdkVersion
-
- override val deviceManufacturer: String = Build.MANUFACTURER
-
- override val deviceModel: String = Build.MODEL
-
- override val osName: String = "Android"
-
- override val osVersion: String = Build.VERSION.RELEASE
-
- override val osBuildId: String = Build.ID
-
- override val sdkWrapper: String? = OneSignalWrapper.sdkType
-
- override val sdkWrapperVersion: String? = OneSignalWrapper.sdkVersion
-
- // Per-event attributes - IDs are cached (calculated once), appState is dynamic (calculated per access)
- override val appId: String? by lazy {
- idResolver.resolveAppId()
- }
-
- override val onesignalId: String? by lazy {
- idResolver.resolveOnesignalId()
- }
-
- override val pushSubscriptionId: String? by lazy {
- idResolver.resolvePushSubscriptionId()
- }
-
- // https://opentelemetry.io/docs/specs/semconv/registry/attributes/android/
- override val appState: String
- @Suppress("TooGenericExceptionCaught", "SwallowedException")
- get() = try {
- // Try to get from ApplicationService if available
- getIsInForeground?.invoke()?.let { isForeground ->
- if (isForeground) "foreground" else "background"
- } ?: run {
- // Fall back to ActivityManager if Context is available
- context?.let { ctx ->
- @Suppress("TooGenericExceptionCaught", "SwallowedException")
- try {
- val activityManager = ctx.getSystemService(Context.ACTIVITY_SERVICE) as? ActivityManager
- val runningAppProcesses = activityManager?.runningAppProcesses
- val currentProcess = runningAppProcesses?.find { it.pid == android.os.Process.myPid() }
- when (currentProcess?.importance) {
- ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND,
- ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE -> "foreground"
- else -> "background"
- }
- } catch (e: Exception) {
- "unknown"
- }
- } ?: "unknown"
- }
- } catch (e: Exception) {
- "unknown"
- }
-
- // https://opentelemetry.io/docs/specs/semconv/system/process-metrics/#metric-processuptime
- override val processUptime: Long
- get() = android.os.SystemClock.uptimeMillis() - android.os.Process.getStartUptimeMillis()
-
- // https://opentelemetry.io/docs/specs/semconv/general/attributes/#general-thread-attributes
- override val currentThreadName: String
- get() = Thread.currentThread().name
-
- override val crashStoragePath: String by lazy {
- val path = config.crashStoragePath
- Logging.info("OneSignal: Crash logs stored at: $path")
- path
- }
-
- override val minFileAgeForReadMillis: Long = 5_000
-
- // Cached from SharedPreferences on first access and held for the session.
- // Mid-session config updates are handled by OtelLifecycleManager reading
- // from ConfigModel directly, not from these cached values.
- override val isRemoteLoggingEnabled: Boolean by lazy {
- idResolver.resolveRemoteLoggingEnabled()
- }
-
- // Cached from SharedPreferences on first access and held for the session.
- // Mid-session config updates are handled by OtelLifecycleManager reading
- // from ConfigModel directly, not from these cached values.
- @Suppress("TooGenericExceptionCaught", "SwallowedException")
- override val remoteLogLevel: String? by lazy {
- try {
- idResolver.resolveRemoteLogLevel()?.name
- } catch (e: Exception) {
- null
- }
- }
-
- override val appIdForHeaders: String
- get() = appId ?: ""
-
- override val apiBaseUrl: String = OneSignalService.ONESIGNAL_API_BASE_URL
-}
-
-/**
- * Factory function to create AndroidOtelPlatformProvider without service dependencies.
- * Reads all values directly from SharedPreferences and system services.
- */
-internal fun createAndroidOtelPlatformProvider(
- context: Context,
-): OtelPlatformProvider {
- val crashStoragePath = context.cacheDir.path + java.io.File.separator +
- "onesignal" + java.io.File.separator +
- "otel" + java.io.File.separator +
- "crashes"
-
- return OtelPlatformProvider(
- OtelPlatformProviderConfig(
- crashStoragePath = crashStoragePath,
- appPackageId = context.packageName,
- appVersion = com.onesignal.common.AndroidUtils.getAppVersion(context) ?: "unknown",
- context = context,
- )
- )
-}
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/internal/OneSignalImp.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/internal/OneSignalImp.kt
index afd7ab39d8..1ccf96809b 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/internal/OneSignalImp.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/internal/OneSignalImp.kt
@@ -53,8 +53,6 @@ import com.onesignal.user.internal.subscriptions.SubscriptionType
import org.json.JSONObject
internal class OneSignalImp : IOneSignal, IServiceProvider {
- private var otelManager: OtelLifecycleManager? = null
-
override val sdkVersion: String = OneSignalUtils.sdkVersion
override var isInitialized: Boolean = false
@@ -204,8 +202,6 @@ internal class OneSignalImp : IOneSignal, IServiceProvider {
Logging.log(LogLevel.DEBUG, "initWithContext: SDK initializing")
- otelManager = OtelLifecycleManager(context).also { it.initializeFromCachedConfig() }
-
PreferenceStoreFix.ensureNoObfuscatedPrefStore(context)
// start the application service. This is called explicitly first because we want
@@ -222,8 +218,6 @@ internal class OneSignalImp : IOneSignal, IServiceProvider {
sessionModel = services.getService().model
operationRepo = services.getService()
- otelManager?.subscribeToConfigStore(services.getService())
-
var forceCreateUser = false
// initWithContext is called by our internal services/receivers/activities but they do not provide
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/internal/OtelConfigEvaluator.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/internal/OtelConfigEvaluator.kt
deleted file mode 100644
index ea8b862ae5..0000000000
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/internal/OtelConfigEvaluator.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-package com.onesignal.internal
-
-import com.onesignal.debug.LogLevel
-
-/**
- * Snapshot of the Otel-relevant fields from remote config.
- * Used by [OtelConfigEvaluator] to diff old vs new config.
- */
-internal data class OtelConfig(
- val isEnabled: Boolean,
- val logLevel: LogLevel?,
-) {
- companion object {
- val DISABLED = OtelConfig(isEnabled = false, logLevel = null)
- }
-}
-
-/**
- * Describes what the [OtelLifecycleManager] should do after a config change.
- */
-internal sealed class OtelConfigAction {
- /** Nothing changed that affects Otel features. */
- object NoChange : OtelConfigAction()
-
- /** Otel features should be started at the given [logLevel]. */
- data class Enable(val logLevel: LogLevel) : OtelConfigAction()
-
- /** The remote log level changed while features remain enabled. */
- data class UpdateLogLevel(val oldLevel: LogLevel, val newLevel: LogLevel) : OtelConfigAction()
-
- /** Otel features should be stopped/torn down. */
- object Disable : OtelConfigAction()
-}
-
-/**
- * Pure, side-effect-free evaluator that compares old and new [OtelConfig]
- * and returns the [OtelConfigAction] the lifecycle manager should execute.
- *
- * Designed to be fully unit-testable without mocks.
- */
-internal object OtelConfigEvaluator {
- /**
- * @param old the previous config snapshot, or null on first evaluation (cold start).
- * @param new the freshly-arrived config snapshot.
- */
- fun evaluate(old: OtelConfig?, new: OtelConfig): OtelConfigAction {
- val wasEnabled = old?.isEnabled == true
- val isNowEnabled = new.isEnabled
-
- return when {
- // Transition: off -> on
- !wasEnabled && isNowEnabled -> {
- val level = new.logLevel ?: LogLevel.ERROR
- OtelConfigAction.Enable(level)
- }
- // Transition: on -> off
- wasEnabled && !isNowEnabled -> OtelConfigAction.Disable
- // Stays enabled but log level changed
- wasEnabled && isNowEnabled && old?.logLevel != new.logLevel -> {
- val oldLevel = old?.logLevel ?: LogLevel.ERROR
- val newLevel = new.logLevel ?: LogLevel.ERROR
- OtelConfigAction.UpdateLogLevel(oldLevel, newLevel)
- }
- // Everything else: no meaningful change
- else -> OtelConfigAction.NoChange
- }
- }
-}
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/internal/OtelLifecycleManager.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/internal/OtelLifecycleManager.kt
deleted file mode 100644
index 1b8b97b58b..0000000000
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/internal/OtelLifecycleManager.kt
+++ /dev/null
@@ -1,240 +0,0 @@
-package com.onesignal.internal
-
-import android.content.Context
-import com.onesignal.common.modeling.ISingletonModelStoreChangeHandler
-import com.onesignal.common.modeling.ModelChangeTags
-import com.onesignal.common.modeling.ModelChangedArgs
-import com.onesignal.core.internal.config.ConfigModel
-import com.onesignal.core.internal.config.ConfigModelStore
-import com.onesignal.debug.LogLevel
-import com.onesignal.debug.internal.crash.AnrConstants
-import com.onesignal.debug.internal.crash.OneSignalCrashHandlerFactory
-import com.onesignal.debug.internal.crash.OtelSdkSupport
-import com.onesignal.debug.internal.crash.createAnrDetector
-import com.onesignal.debug.internal.logging.Logging
-import com.onesignal.debug.internal.logging.otel.android.AndroidOtelLogger
-import com.onesignal.debug.internal.logging.otel.android.OtelPlatformProvider
-import com.onesignal.debug.internal.logging.otel.android.createAndroidOtelPlatformProvider
-import com.onesignal.otel.IOtelCrashHandler
-import com.onesignal.otel.IOtelLogger
-import com.onesignal.otel.IOtelOpenTelemetryRemote
-import com.onesignal.otel.IOtelPlatformProvider
-import com.onesignal.otel.OtelFactory
-import com.onesignal.otel.crash.IOtelAnrDetector
-
-/**
- * Owns the lifecycle of all Otel-based observability features and reacts
- * to remote config changes so features can be enabled, disabled, or
- * have their log level updated mid-session.
- *
- * Subscribes to [ConfigModelStore] via [ISingletonModelStoreChangeHandler]
- * so that when fresh remote config arrives (HYDRATE), Otel features are
- * automatically started, stopped, or updated.
- *
- * Thread safety: methods are synchronized on [lock] so that concurrent
- * calls from initEssentials (main) and the config store callback (IO) are safe.
- *
- * All factory parameters default to the real implementations, so production
- * callers can use `OtelLifecycleManager(context)`. Tests can override any
- * factory to inject mocks or throwing stubs.
- */
-@Suppress("TooManyFunctions")
-internal class OtelLifecycleManager(
- private val context: Context,
- private val crashHandlerFactory: (Context, IOtelLogger) -> IOtelCrashHandler =
- { ctx, log -> OneSignalCrashHandlerFactory.createCrashHandler(ctx, log) },
- private val anrDetectorFactory: (IOtelPlatformProvider, IOtelLogger, Long, Long) -> IOtelAnrDetector =
- { pp, log, threshold, interval -> createAnrDetector(pp, log, threshold, interval) },
- private val remoteTelemetryFactory: (IOtelPlatformProvider) -> IOtelOpenTelemetryRemote =
- { pp -> OtelFactory.createRemoteTelemetry(pp) },
- private val platformProviderFactory: (Context) -> OtelPlatformProvider =
- { ctx -> createAndroidOtelPlatformProvider(ctx) },
- private val loggerFactory: () -> IOtelLogger = { AndroidOtelLogger() },
-) : ISingletonModelStoreChangeHandler {
- private val lock = Any()
-
- private val platformProvider: OtelPlatformProvider by lazy {
- platformProviderFactory(context)
- }
-
- private val logger: IOtelLogger by lazy { loggerFactory() }
-
- private var crashHandler: IOtelCrashHandler? = null
- private var anrDetector: IOtelAnrDetector? = null
- private var remoteTelemetry: IOtelOpenTelemetryRemote? = null
- private var currentConfig: OtelConfig? = null
-
- /**
- * Called once from [OneSignalImp.initEssentials] at cold start.
- * Reads the cached config from SharedPreferences and boots
- * whichever features are already enabled.
- */
- @Suppress("TooGenericExceptionCaught")
- fun initializeFromCachedConfig() {
- if (!OtelSdkSupport.isSupported) {
- Logging.info("OneSignal: Device SDK < ${OtelSdkSupport.MIN_SDK_VERSION}, Otel not supported — skipping all Otel features")
- return
- }
-
- try {
- val cachedConfig = readCurrentCachedConfig()
- synchronized(lock) {
- val action = OtelConfigEvaluator.evaluate(old = currentConfig, new = cachedConfig)
- applyAction(action, cachedConfig)
- }
- } catch (t: Throwable) {
- Logging.warn("OneSignal: Failed to initialize Otel from cached config: ${t.message}", t)
- }
- }
-
- /**
- * Subscribes this manager to config store change events.
- * Call after the IoC container is bootstrapped (i.e. after [bootstrapServices]).
- */
- fun subscribeToConfigStore(configModelStore: ConfigModelStore) {
- configModelStore.subscribe(this)
- }
-
- // ------------------------------------------------------------------
- // ISingletonModelStoreChangeHandler
- // ------------------------------------------------------------------
-
- @Suppress("TooGenericExceptionCaught")
- override fun onModelReplaced(model: ConfigModel, tag: String) {
- if (tag != ModelChangeTags.HYDRATE) return
- if (!OtelSdkSupport.isSupported) return
-
- try {
- val logLevel = model.remoteLoggingParams.logLevel
- val isEnabled = model.remoteLoggingParams.isEnabled
- val newConfig = OtelConfig(isEnabled = isEnabled, logLevel = logLevel)
- synchronized(lock) {
- val action = OtelConfigEvaluator.evaluate(old = currentConfig, new = newConfig)
- applyAction(action, newConfig)
- }
- } catch (t: Throwable) {
- Logging.warn("OneSignal: Failed to refresh Otel from remote config: ${t.message}", t)
- }
- }
-
- override fun onModelUpdated(args: ModelChangedArgs, tag: String) {
- // We only care about full model replacements (HYDRATE), not individual property changes.
- }
-
- // ------------------------------------------------------------------
- // Internal
- // ------------------------------------------------------------------
-
- private fun readCurrentCachedConfig(): OtelConfig {
- val enabled = platformProvider.isRemoteLoggingEnabled
- val level = LogLevel.fromString(platformProvider.remoteLogLevel)
- return OtelConfig(isEnabled = enabled, logLevel = level)
- }
-
- /** Must be called while holding [lock]. */
- @Suppress("TooGenericExceptionCaught")
- private fun applyAction(action: OtelConfigAction, newConfig: OtelConfig) {
- when (action) {
- is OtelConfigAction.Enable -> enableFeatures(newConfig.logLevel ?: LogLevel.ERROR)
- is OtelConfigAction.Disable -> disableFeatures()
- is OtelConfigAction.UpdateLogLevel -> updateLogLevel(action.newLevel)
- is OtelConfigAction.NoChange -> {
- Logging.debug("OneSignal: Otel config unchanged, no action needed")
- }
- }
- currentConfig = newConfig
- }
-
- @Suppress("TooGenericExceptionCaught")
- private fun enableFeatures(logLevel: LogLevel) {
- Logging.info("OneSignal: Enabling Otel features at level $logLevel")
-
- try {
- startCrashHandler()
- } catch (t: Throwable) {
- Logging.warn("OneSignal: Failed to start crash handler: ${t.message}", t)
- }
-
- try {
- startAnrDetector()
- } catch (t: Throwable) {
- Logging.warn("OneSignal: Failed to start ANR detector: ${t.message}", t)
- }
-
- try {
- startOtelLogging(logLevel)
- } catch (t: Throwable) {
- Logging.warn("OneSignal: Failed to start Otel logging: ${t.message}", t)
- }
- }
-
- @Suppress("TooGenericExceptionCaught")
- private fun disableFeatures() {
- Logging.info("OneSignal: Disabling Otel features")
-
- try {
- anrDetector?.stop()
- anrDetector = null
- } catch (t: Throwable) {
- Logging.warn("OneSignal: Error stopping ANR detector: ${t.message}", t)
- }
-
- try {
- crashHandler?.unregister()
- crashHandler = null
- } catch (t: Throwable) {
- Logging.warn("OneSignal: Error unregistering crash handler: ${t.message}", t)
- }
-
- try {
- Logging.setOtelTelemetry(null, { false })
- remoteTelemetry?.shutdown()
- remoteTelemetry = null
- } catch (t: Throwable) {
- Logging.warn("OneSignal: Error disabling Otel logging: ${t.message}", t)
- }
- }
-
- @Suppress("TooGenericExceptionCaught")
- private fun updateLogLevel(newLevel: LogLevel) {
- Logging.info("OneSignal: Updating Otel log level to $newLevel")
- try {
- startOtelLogging(newLevel)
- } catch (t: Throwable) {
- Logging.warn("OneSignal: Failed to update Otel log level: ${t.message}", t)
- }
- }
-
- private fun startCrashHandler() {
- if (crashHandler != null) return
- val handler = crashHandlerFactory(context, logger)
- handler.initialize()
- crashHandler = handler
- Logging.info("OneSignal: Crash handler initialized — logs at: ${platformProvider.crashStoragePath}")
- }
-
- private fun startAnrDetector() {
- if (anrDetector != null) return
- val detector = anrDetectorFactory(
- platformProvider,
- logger,
- AnrConstants.DEFAULT_ANR_THRESHOLD_MS,
- AnrConstants.DEFAULT_CHECK_INTERVAL_MS,
- )
- detector.start()
- anrDetector = detector
- Logging.info("OneSignal: ANR detector started")
- }
-
- @Suppress("TooGenericExceptionCaught")
- private fun startOtelLogging(logLevel: LogLevel) {
- remoteTelemetry?.shutdown()
- val telemetry = remoteTelemetryFactory(platformProvider)
- remoteTelemetry = telemetry
- val shouldSend: (LogLevel) -> Boolean = { level ->
- logLevel != LogLevel.NONE && level <= logLevel
- }
- Logging.setOtelTelemetry(telemetry, shouldSend)
- Logging.info("OneSignal: Otel logging active at level $logLevel")
- }
-}
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventsController.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventsController.kt
index 17137a0665..70758efe36 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventsController.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventsController.kt
@@ -80,9 +80,9 @@ internal class OutcomeEventsController(
val err = "OutcomeEventsController.sendSavedOutcomeEvent: Sending outcome with name: ${event.outcomeId} failed with status code: ${ex.statusCode} and response: ${ex.response}"
if (responseType == NetworkUtils.ResponseStatusType.RETRYABLE) {
- Logging.info("$err Outcome event was cached and will be reattempted on app cold start")
+ Logging.warn("$err Outcome event was cached and will be reattempted on app cold start")
} else {
- Logging.warn("$err Outcome event will be omitted!")
+ Logging.error("$err Outcome event will be omitted!")
_outcomeEventsCache.deleteOldOutcomeEvent(event)
}
}
@@ -223,13 +223,13 @@ internal class OutcomeEventsController(
val err = "OutcomeEventsController.sendAndCreateOutcomeEvent: Sending outcome with name: $name failed with status code: ${ex.statusCode} and response: ${ex.response}"
if (responseType == NetworkUtils.ResponseStatusType.RETRYABLE) {
- Logging.info("$err Outcome event was cached and will be reattempted on app cold start")
+ Logging.warn("$err Outcome event was cached and will be reattempted on app cold start")
// Only if we need to save and retry the outcome, then we will save the timestamp for future sending
eventParams.timestamp = timestampSeconds
_outcomeEventsCache.saveOutcomeEvent(eventParams)
} else {
- Logging.warn("$err Outcome event will be omitted!")
+ Logging.error("$err Outcome event will be omitted!")
_outcomeEventsCache.deleteOldOutcomeEvent(eventParams)
}
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/session/impl/SessionListener.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/session/impl/SessionListener.kt
index 2f4f3f8ce2..acef72d3c5 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/session/impl/SessionListener.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/session/impl/SessionListener.kt
@@ -56,7 +56,7 @@ internal class SessionListener(
// Time is erroneous if below 1 second or over a day
if (durationInSeconds < 1L || durationInSeconds > SECONDS_IN_A_DAY) {
- Logging.info("SessionListener.onSessionEnded sending duration of $durationInSeconds seconds")
+ Logging.error("SessionListener.onSessionEnded sending duration of $durationInSeconds seconds")
}
_operationRepo.enqueue(
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/identity/IdentityModelStore.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/identity/IdentityModelStore.kt
index 35ff97298f..911c4ba71b 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/identity/IdentityModelStore.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/identity/IdentityModelStore.kt
@@ -4,8 +4,6 @@ import com.onesignal.common.modeling.SimpleModelStore
import com.onesignal.common.modeling.SingletonModelStore
import com.onesignal.core.internal.preferences.IPreferencesService
-const val IDENTITY_NAME_SPACE = "identity"
-
open class IdentityModelStore(prefs: IPreferencesService) : SingletonModelStore(
- SimpleModelStore({ IdentityModel() }, IDENTITY_NAME_SPACE, prefs),
+ SimpleModelStore({ IdentityModel() }, "identity", prefs),
)
diff --git a/OneSignalSDK/onesignal/core/src/test/AndroidManifest.xml b/OneSignalSDK/onesignal/core/src/test/AndroidManifest.xml
deleted file mode 100644
index 8768713b9a..0000000000
--- a/OneSignalSDK/onesignal/core/src/test/AndroidManifest.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/LoggingTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/LoggingTests.kt
index 4f9c377348..ca6ce9b308 100644
--- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/LoggingTests.kt
+++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/LoggingTests.kt
@@ -95,13 +95,13 @@ class LoggingTests : FunSpec({
test("removeListener nested") {
// Given
val calls = ArrayList()
- lateinit var listener: ILogListener
- listener = ILogListener { logEvent ->
- calls += logEvent.entry
- // Remove self from listeners
- Logging.removeListener(listener)
- }
- Logging.addListener(listener)
+ var listener: ILogListener? = null
+ listener =
+ ILogListener {
+ calls += it.entry
+ Logging.removeListener(listener!!)
+ }
+ Logging.addListener(listener!!)
// When
Logging.debug("test")
diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/crash/OneSignalCrashHandlerFactoryTest.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/crash/OneSignalCrashHandlerFactoryTest.kt
deleted file mode 100644
index 5eaaa714d7..0000000000
--- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/crash/OneSignalCrashHandlerFactoryTest.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-package com.onesignal.debug.internal.crash
-
-import android.content.Context
-import android.os.Build
-import androidx.test.core.app.ApplicationProvider
-import br.com.colman.kotest.android.extensions.robolectric.RobolectricTest
-import com.onesignal.debug.internal.logging.otel.android.AndroidOtelLogger
-import com.onesignal.otel.IOtelCrashHandler
-import com.onesignal.otel.IOtelLogger
-import io.kotest.core.spec.style.FunSpec
-import io.kotest.matchers.shouldNotBe
-import io.kotest.matchers.types.shouldBeInstanceOf
-import io.mockk.mockk
-import org.robolectric.annotation.Config
-
-@RobolectricTest
-@Config(sdk = [Build.VERSION_CODES.O])
-class OneSignalCrashHandlerFactoryTest : FunSpec({
- lateinit var appContext: Context
- lateinit var logger: AndroidOtelLogger
- // Save original handler to restore after tests
- val originalHandler: Thread.UncaughtExceptionHandler? = Thread.getDefaultUncaughtExceptionHandler()
-
- beforeAny {
- appContext = ApplicationProvider.getApplicationContext()
- logger = AndroidOtelLogger()
- }
-
- afterEach {
- // Restore original uncaught exception handler after each test
- Thread.setDefaultUncaughtExceptionHandler(originalHandler)
- }
-
- test("createCrashHandler should return IOtelCrashHandler") {
- val handler = OneSignalCrashHandlerFactory.createCrashHandler(appContext, logger)
-
- handler.shouldBeInstanceOf()
- }
-
- test("createCrashHandler should create handler that can be initialized") {
- val handler = OneSignalCrashHandlerFactory.createCrashHandler(appContext, logger)
-
- handler shouldNotBe null
- handler.initialize()
- }
-
- test("createCrashHandler should accept mock logger") {
- val mockLogger = mockk(relaxed = true)
-
- val handler = OneSignalCrashHandlerFactory.createCrashHandler(appContext, mockLogger)
-
- handler shouldNotBe null
- handler.shouldBeInstanceOf()
- }
-
- test("handler should be idempotent when initialized multiple times") {
- val handler = OneSignalCrashHandlerFactory.createCrashHandler(appContext, logger)
-
- handler.initialize()
- handler.initialize() // Should not throw
-
- handler shouldNotBe null
- }
-
- test("createCrashHandler should work with different contexts") {
- val context1: Context = ApplicationProvider.getApplicationContext()
- val context2: Context = ApplicationProvider.getApplicationContext()
-
- val handler1 = OneSignalCrashHandlerFactory.createCrashHandler(context1, logger)
- val handler2 = OneSignalCrashHandlerFactory.createCrashHandler(context2, logger)
-
- handler1 shouldNotBe null
- handler2 shouldNotBe null
- }
-})
diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/crash/OneSignalCrashUploaderWrapperTest.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/crash/OneSignalCrashUploaderWrapperTest.kt
deleted file mode 100644
index 942c02af20..0000000000
--- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/crash/OneSignalCrashUploaderWrapperTest.kt
+++ /dev/null
@@ -1,104 +0,0 @@
-package com.onesignal.debug.internal.crash
-
-import android.content.Context
-import android.content.SharedPreferences
-import android.os.Build
-import androidx.test.core.app.ApplicationProvider
-import br.com.colman.kotest.android.extensions.robolectric.RobolectricTest
-import com.onesignal.core.internal.application.IApplicationService
-import com.onesignal.core.internal.config.ConfigModel
-import com.onesignal.core.internal.preferences.PreferenceOneSignalKeys
-import com.onesignal.core.internal.preferences.PreferenceStores
-import com.onesignal.core.internal.startup.IStartableService
-import io.kotest.core.spec.style.FunSpec
-import io.kotest.matchers.shouldNotBe
-import io.kotest.matchers.types.shouldBeInstanceOf
-import io.mockk.every
-import io.mockk.mockk
-import kotlinx.coroutines.runBlocking
-import org.json.JSONArray
-import org.json.JSONObject
-import org.robolectric.annotation.Config
-import com.onesignal.core.internal.config.CONFIG_NAME_SPACE as configNameSpace
-
-@RobolectricTest
-@Config(sdk = [Build.VERSION_CODES.O])
-class OneSignalCrashUploaderWrapperTest : FunSpec({
-
- lateinit var appContext: Context
- lateinit var sharedPreferences: SharedPreferences
-
- beforeAny {
- appContext = ApplicationProvider.getApplicationContext()
- sharedPreferences = appContext.getSharedPreferences(PreferenceStores.ONESIGNAL, Context.MODE_PRIVATE)
- }
-
- afterEach {
- sharedPreferences.edit().clear().commit()
- }
-
- test("should implement IStartableService interface") {
- val mockApplicationService = mockk(relaxed = true)
- every { mockApplicationService.appContext } returns appContext
-
- val wrapper = OneSignalCrashUploaderWrapper(mockApplicationService)
-
- wrapper.shouldBeInstanceOf()
- }
-
- test("start should complete without error when remote logging is disabled") {
- // Configure remote logging as disabled (NONE)
- val remoteLoggingParams = JSONObject().put("logLevel", "NONE")
- val configModel = JSONObject().put(ConfigModel::remoteLoggingParams.name, remoteLoggingParams)
- sharedPreferences.edit()
- .putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, JSONArray().put(configModel).toString())
- .commit()
-
- val mockApplicationService = mockk(relaxed = true)
- every { mockApplicationService.appContext } returns appContext
-
- val wrapper = OneSignalCrashUploaderWrapper(mockApplicationService)
-
- // Should return early without error when remote logging is disabled
- runBlocking { wrapper.start() }
- }
-
- test("start should complete without error when no crash reports exist") {
- // Configure remote logging as enabled
- val remoteLoggingParams = JSONObject().put("logLevel", "ERROR")
- val configModel = JSONObject().put(ConfigModel::remoteLoggingParams.name, remoteLoggingParams)
- sharedPreferences.edit()
- .putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, JSONArray().put(configModel).toString())
- .commit()
-
- val mockApplicationService = mockk(relaxed = true)
- every { mockApplicationService.appContext } returns appContext
-
- val wrapper = OneSignalCrashUploaderWrapper(mockApplicationService)
-
- // Should complete without error even when no crash reports exist
- runBlocking { wrapper.start() }
- }
-
- test("start can be called multiple times safely") {
- val mockApplicationService = mockk(relaxed = true)
- every { mockApplicationService.appContext } returns appContext
-
- val wrapper = OneSignalCrashUploaderWrapper(mockApplicationService)
-
- // Multiple calls should not throw
- runBlocking {
- wrapper.start()
- wrapper.start()
- }
- }
-
- test("wrapper should be non-null after creation") {
- val mockApplicationService = mockk(relaxed = true)
- every { mockApplicationService.appContext } returns appContext
-
- val wrapper = OneSignalCrashUploaderWrapper(mockApplicationService)
-
- wrapper shouldNotBe null
- }
-})
diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/crash/OtelAnrDetectorTest.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/crash/OtelAnrDetectorTest.kt
deleted file mode 100644
index 25cac810c6..0000000000
--- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/crash/OtelAnrDetectorTest.kt
+++ /dev/null
@@ -1,221 +0,0 @@
-package com.onesignal.debug.internal.crash
-
-import android.os.Build
-import br.com.colman.kotest.android.extensions.robolectric.RobolectricTest
-import com.onesignal.otel.IOtelLogger
-import com.onesignal.otel.IOtelOpenTelemetryCrash
-import com.onesignal.otel.IOtelPlatformProvider
-import com.onesignal.otel.crash.IOtelAnrDetector
-import io.kotest.core.spec.style.FunSpec
-import io.kotest.matchers.shouldBe
-import io.kotest.matchers.shouldNotBe
-import io.kotest.matchers.types.shouldBeInstanceOf
-import io.mockk.coEvery
-import io.mockk.every
-import io.mockk.mockk
-import io.mockk.verify
-import org.robolectric.annotation.Config
-
-@RobolectricTest
-@Config(sdk = [Build.VERSION_CODES.O])
-class OtelAnrDetectorTest : FunSpec({
-
- val mockPlatformProvider = mockk(relaxed = true)
- val mockLogger = mockk(relaxed = true)
- val mockCrashTelemetry = mockk(relaxed = true)
-
- fun setupDefaultMocks() {
- every { mockPlatformProvider.sdkBase } returns "android"
- every { mockPlatformProvider.sdkBaseVersion } returns "1.0.0"
- every { mockPlatformProvider.appPackageId } returns "com.test.app"
- every { mockPlatformProvider.appVersion } returns "1.0"
- every { mockPlatformProvider.deviceManufacturer } returns "Test"
- every { mockPlatformProvider.deviceModel } returns "TestDevice"
- every { mockPlatformProvider.osName } returns "Android"
- every { mockPlatformProvider.osVersion } returns "10"
- every { mockPlatformProvider.osBuildId } returns "TEST123"
- every { mockPlatformProvider.sdkWrapper } returns null
- every { mockPlatformProvider.sdkWrapperVersion } returns null
- every { mockPlatformProvider.appId } returns "test-app-id"
- every { mockPlatformProvider.onesignalId } returns null
- every { mockPlatformProvider.pushSubscriptionId } returns null
- every { mockPlatformProvider.appState } returns "foreground"
- every { mockPlatformProvider.processUptime } returns 100L
- every { mockPlatformProvider.currentThreadName } returns "main"
- every { mockPlatformProvider.crashStoragePath } returns "/test/path"
- every { mockPlatformProvider.minFileAgeForReadMillis } returns 5000L
- every { mockPlatformProvider.remoteLogLevel } returns "ERROR"
- every { mockPlatformProvider.appIdForHeaders } returns "test-app-id"
- every { mockPlatformProvider.apiBaseUrl } returns "https://api.onesignal.com"
- coEvery { mockPlatformProvider.getInstallId() } returns "test-install-id"
- }
-
- beforeEach {
- setupDefaultMocks()
- }
-
- // ===== Factory Function Tests =====
-
- test("createAnrDetector should return IOtelAnrDetector") {
- // When
- val detector = createAnrDetector(mockPlatformProvider, mockLogger)
-
- // Then
- detector.shouldBeInstanceOf()
- }
-
- test("createAnrDetector should create detector with default thresholds") {
- // When
- val detector = createAnrDetector(mockPlatformProvider, mockLogger)
-
- // Then
- detector shouldNotBe null
- }
-
- test("createAnrDetector should accept custom thresholds") {
- // When
- val detector = createAnrDetector(
- mockPlatformProvider,
- mockLogger,
- anrThresholdMs = 10_000L,
- checkIntervalMs = 2_000L
- )
-
- // Then
- detector shouldNotBe null
- }
-
- // ===== Start/Stop Tests =====
-
- test("start should log info messages") {
- // Given
- val detector = createAnrDetector(mockPlatformProvider, mockLogger)
-
- // When
- detector.start()
-
- // Then
- verify { mockLogger.info(match { it.contains("Starting ANR detection") }) }
-
- // Cleanup
- detector.stop()
- }
-
- test("stop should log info messages") {
- // Given
- val detector = createAnrDetector(mockPlatformProvider, mockLogger)
- detector.start()
-
- // When
- detector.stop()
-
- // Then
- verify { mockLogger.info(match { it.contains("Stopping ANR detection") }) }
- }
-
- test("start should warn when already monitoring") {
- // Given
- val detector = createAnrDetector(mockPlatformProvider, mockLogger)
- detector.start()
-
- // When - start again
- detector.start()
-
- // Then
- verify { mockLogger.warn(match { it.contains("Already monitoring") }) }
-
- // Cleanup
- detector.stop()
- }
-
- test("stop should warn when not monitoring") {
- // Given
- val detector = createAnrDetector(mockPlatformProvider, mockLogger)
-
- // When - stop without starting
- detector.stop()
-
- // Then
- verify { mockLogger.warn(match { it.contains("Not monitoring") }) }
- }
-
- test("start and stop can be called multiple times safely") {
- // Given
- val detector = createAnrDetector(mockPlatformProvider, mockLogger)
-
- // When
- detector.start()
- detector.stop()
- detector.start()
- detector.stop()
-
- // Then - no exceptions thrown
- }
-
- // ===== OtelAnrDetector Internal Tests =====
-
- test("OtelAnrDetector should implement IOtelAnrDetector") {
- // Given
- val detector = OtelAnrDetector(mockCrashTelemetry, mockLogger)
-
- // Then
- detector.shouldBeInstanceOf()
- }
-
- test("OtelAnrDetector should accept custom thresholds") {
- // When
- val detector = OtelAnrDetector(
- mockCrashTelemetry,
- mockLogger,
- anrThresholdMs = 15_000L,
- checkIntervalMs = 3_000L
- )
-
- // Then
- detector shouldNotBe null
- }
-
- test("OtelAnrDetector start should initialize watchdog thread") {
- // Given
- val detector = OtelAnrDetector(
- mockCrashTelemetry,
- mockLogger,
- anrThresholdMs = 100_000L, // Very long threshold to prevent actual ANR detection
- checkIntervalMs = 100_000L // Very long interval
- )
-
- // When
- detector.start()
-
- // Then
- verify { mockLogger.info(match { it.contains("ANR detection started successfully") }) }
-
- // Cleanup
- detector.stop()
- }
-
- test("OtelAnrDetector stop should stop watchdog thread") {
- // Given
- val detector = OtelAnrDetector(
- mockCrashTelemetry,
- mockLogger,
- anrThresholdMs = 100_000L,
- checkIntervalMs = 100_000L
- )
- detector.start()
-
- // When
- detector.stop()
-
- // Then
- verify { mockLogger.info(match { it.contains("ANR detection stopped") }) }
- }
-
- // ===== AnrConstants Tests =====
-
- test("AnrConstants should have reasonable defaults") {
- // Then
- AnrConstants.DEFAULT_ANR_THRESHOLD_MS shouldBe 5_000L
- AnrConstants.DEFAULT_CHECK_INTERVAL_MS shouldBe 2_000L
- }
-})
diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/crash/OtelIntegrationTest.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/crash/OtelIntegrationTest.kt
deleted file mode 100644
index f7cf09c7dd..0000000000
--- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/crash/OtelIntegrationTest.kt
+++ /dev/null
@@ -1,167 +0,0 @@
-package com.onesignal.debug.internal.crash
-
-import android.content.Context
-import android.content.SharedPreferences
-import android.os.Build
-import androidx.test.core.app.ApplicationProvider
-import br.com.colman.kotest.android.extensions.robolectric.RobolectricTest
-import com.onesignal.core.internal.config.ConfigModel
-import com.onesignal.core.internal.preferences.PreferenceOneSignalKeys
-import com.onesignal.core.internal.preferences.PreferenceStores
-import com.onesignal.debug.internal.logging.otel.android.AndroidOtelLogger
-import com.onesignal.debug.internal.logging.otel.android.createAndroidOtelPlatformProvider
-import com.onesignal.otel.IOtelCrashHandler
-import com.onesignal.otel.IOtelPlatformProvider
-import com.onesignal.otel.OtelFactory
-import com.onesignal.user.internal.backend.IdentityConstants
-import io.kotest.core.spec.style.FunSpec
-import io.kotest.matchers.shouldBe
-import io.kotest.matchers.shouldNotBe
-import io.kotest.matchers.types.shouldBeInstanceOf
-import kotlinx.coroutines.runBlocking
-import org.json.JSONArray
-import org.json.JSONObject
-import org.robolectric.annotation.Config
-import com.onesignal.core.internal.config.CONFIG_NAME_SPACE as configNameSpace
-import com.onesignal.user.internal.identity.IDENTITY_NAME_SPACE as identityNameSpace
-
-// Helper extension for shouldBeOneOf
-private infix fun T.shouldBeOneOf(expected: List) {
- val isInList = expected.contains(this)
- if (!isInList) {
- throw AssertionError("Expected $this to be one of $expected")
- }
-}
-
-@RobolectricTest
-@Config(sdk = [Build.VERSION_CODES.O])
-class OtelIntegrationTest : FunSpec({
- var appContext: Context? = null
- var sharedPreferences: SharedPreferences? = null
-
- beforeAny {
- if (appContext == null) {
- appContext = ApplicationProvider.getApplicationContext()
- sharedPreferences = appContext!!.getSharedPreferences(PreferenceStores.ONESIGNAL, Context.MODE_PRIVATE)
- }
- }
-
- beforeEach {
- // Ensure sharedPreferences is initialized
- if (sharedPreferences == null) {
- appContext = ApplicationProvider.getApplicationContext()
- sharedPreferences = appContext!!.getSharedPreferences(PreferenceStores.ONESIGNAL, Context.MODE_PRIVATE)
- }
- // Clear and set up SharedPreferences with test data
- sharedPreferences!!.edit().clear().commit()
-
- // Set up ConfigModelStore data
- val configModel = JSONObject().apply {
- put(ConfigModel::appId.name, "test-app-id")
- put(ConfigModel::pushSubscriptionId.name, "test-subscription-id")
- val remoteLoggingParams = JSONObject().apply {
- put("logLevel", "ERROR")
- }
- put(ConfigModel::remoteLoggingParams.name, remoteLoggingParams)
- }
- val configArray = JSONArray().apply {
- put(configModel)
- }
-
- // Set up IdentityModelStore data
- val identityModel = JSONObject().apply {
- put(IdentityConstants.ONESIGNAL_ID, "test-onesignal-id")
- }
- val identityArray = JSONArray().apply {
- put(identityModel)
- }
-
- sharedPreferences.edit()
- .putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, configArray.toString())
- .putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + identityNameSpace, identityArray.toString())
- .putString(PreferenceOneSignalKeys.PREFS_OS_INSTALL_ID, "test-install-id")
- .commit()
- }
-
- afterEach {
- sharedPreferences!!.edit().clear().commit()
- }
-
- test("AndroidOtelPlatformProvider should provide correct Android values") {
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- provider.shouldBeInstanceOf()
- provider.sdkBase shouldBe "android"
- provider.appPackageId shouldBe appContext!!.packageName // Use actual package name from context
- provider.osName shouldBe "Android"
- provider.deviceManufacturer shouldBe Build.MANUFACTURER
- provider.deviceModel shouldBe Build.MODEL
- provider.osVersion shouldBe Build.VERSION.RELEASE
- provider.osBuildId shouldBe Build.ID
-
- runBlocking {
- provider.getInstallId() shouldNotBe null
- }
- }
-
- test("AndroidOtelPlatformProvider should provide per-event values") {
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- provider.appId shouldBe "test-app-id"
- provider.onesignalId shouldBe "test-onesignal-id"
- provider.pushSubscriptionId shouldBe "test-subscription-id"
- provider.appState shouldBeOneOf listOf("foreground", "background", "unknown")
- (provider.processUptime > 0) shouldBe true
- provider.currentThreadName shouldBe Thread.currentThread().name
- }
-
- test("AndroidOtelLogger should delegate to Logging") {
- val logger = AndroidOtelLogger()
-
- logger.shouldBeInstanceOf()
- // Should not throw
- logger.debug("test")
- logger.info("test")
- logger.warn("test")
- logger.error("test")
- }
-
- test("OtelFactory should create crash handler with Android provider") {
- val provider = createAndroidOtelPlatformProvider(appContext!!)
- val logger = AndroidOtelLogger()
-
- val handler = OtelFactory.createCrashHandler(provider, logger)
-
- handler shouldNotBe null
- handler.shouldBeInstanceOf()
- handler.initialize() // Should not throw
- }
-
- test("OneSignalCrashHandlerFactory should create working crash handler") {
- // Note: OneSignalCrashHandlerFactory may need to be updated to use the new approach
- // For now, we'll test the direct creation
- val provider = createAndroidOtelPlatformProvider(appContext!!)
- val logger = AndroidOtelLogger()
- val handler = OtelFactory.createCrashHandler(provider, logger)
-
- handler shouldNotBe null
- handler.shouldBeInstanceOf()
- handler.initialize() // Should not throw
- }
-
- test("AndroidOtelPlatformProvider should provide crash storage path") {
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- provider.crashStoragePath.contains("onesignal") shouldBe true
- provider.crashStoragePath.contains("otel") shouldBe true
- provider.crashStoragePath.contains("crashes") shouldBe true
- provider.minFileAgeForReadMillis shouldBe 5000L
- }
-
- test("AndroidOtelPlatformProvider should handle remote logging config") {
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- provider.remoteLogLevel shouldBe "ERROR"
- provider.appIdForHeaders shouldBe "test-app-id"
- }
-})
diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/crash/OtelSdkSupportTest.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/crash/OtelSdkSupportTest.kt
deleted file mode 100644
index f7660108e8..0000000000
--- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/crash/OtelSdkSupportTest.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.onesignal.debug.internal.crash
-
-import android.os.Build
-import br.com.colman.kotest.android.extensions.robolectric.RobolectricTest
-import io.kotest.core.spec.style.FunSpec
-import io.kotest.matchers.shouldBe
-import org.robolectric.annotation.Config
-
-@RobolectricTest
-@Config(sdk = [Build.VERSION_CODES.O])
-class OtelSdkSupportTest : FunSpec({
-
- afterEach {
- OtelSdkSupport.reset()
- }
-
- test("isSupported is true on SDK >= 26") {
- OtelSdkSupport.reset()
- OtelSdkSupport.isSupported shouldBe true
- }
-
- test("isSupported can be overridden to false for testing") {
- OtelSdkSupport.isSupported = false
- OtelSdkSupport.isSupported shouldBe false
- }
-
- test("reset restores runtime-detected value") {
- OtelSdkSupport.isSupported = false
- OtelSdkSupport.isSupported shouldBe false
-
- OtelSdkSupport.reset()
- OtelSdkSupport.isSupported shouldBe true
- }
-
- test("MIN_SDK_VERSION is 26") {
- OtelSdkSupport.MIN_SDK_VERSION shouldBe 26
- }
-})
diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/logging/LoggingOtelTest.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/logging/LoggingOtelTest.kt
deleted file mode 100644
index 6bde1defb8..0000000000
--- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/logging/LoggingOtelTest.kt
+++ /dev/null
@@ -1,232 +0,0 @@
-package com.onesignal.debug.internal.logging
-
-import android.os.Build
-import br.com.colman.kotest.android.extensions.robolectric.RobolectricTest
-import com.onesignal.debug.LogLevel
-import com.onesignal.otel.IOtelOpenTelemetryRemote
-import io.kotest.core.spec.style.FunSpec
-import io.mockk.mockk
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.runBlocking
-import org.robolectric.annotation.Config
-
-@RobolectricTest
-@Config(sdk = [Build.VERSION_CODES.O])
-class LoggingOtelTest : FunSpec({
- val mockTelemetry = mockk(relaxed = true)
-
- beforeEach {
- // Reset Logging state
- Logging.setOtelTelemetry(null, { false })
-
- // Setup default mock behavior - relaxed mock automatically returns mocks for suspend functions
- // The return type (LogRecordBuilder) is handled by the relaxed mock, but we can't verify it
- // directly due to type visibility. We'll test behavior instead.
- }
-
- test("setOtelTelemetry should store telemetry and enabled check function") {
- // Given
- val shouldSend = { _: LogLevel -> true }
-
- // When
- Logging.setOtelTelemetry(mockTelemetry, shouldSend)
-
- // Then - verify it's set (we'll test it works by logging)
- Logging.info("test")
-
- // Wait for async logging
- runBlocking {
- delay(100)
- }
-
- // Then - verify it doesn't crash (integration test)
- // Note: We can't verify exact calls due to OpenTelemetry type visibility
- }
-
- test("logToOtel should work when remote logging is enabled") {
- // Given
- Logging.setOtelTelemetry(mockTelemetry, { _: LogLevel -> true })
-
- // When
- Logging.info("test message")
-
- // Wait for async logging
- runBlocking {
- delay(100)
- }
-
- // Then - should not crash (integration test)
- // The actual Otel call is verified in otel module tests
- }
-
- test("logToOtel should NOT crash when remote logging is disabled") {
- // Given
- Logging.setOtelTelemetry(mockTelemetry, { _: LogLevel -> false })
-
- // When
- Logging.info("test message")
-
- // Wait for async logging
- runBlocking {
- delay(100)
- }
-
- // Then - should not crash
- }
-
- test("logToOtel should NOT crash when telemetry is null") {
- // Given
- Logging.setOtelTelemetry(null, { _: LogLevel -> true })
-
- // When
- Logging.info("test message")
-
- // Wait for async logging
- runBlocking {
- delay(100)
- }
-
- // Then - should not crash
- }
-
- test("logToOtel should handle all log levels without crashing") {
- // Given
- Logging.setOtelTelemetry(mockTelemetry, { _: LogLevel -> true })
-
- // When
- Logging.verbose("verbose message")
- Logging.debug("debug message")
- Logging.info("info message")
- Logging.warn("warn message")
- Logging.error("error message")
- Logging.fatal("fatal message")
-
- // Wait for async logging
- runBlocking {
- delay(200)
- }
-
- // Then - should not crash for any level
- }
-
- test("logToOtel should NOT log NONE level") {
- // Given
- Logging.setOtelTelemetry(mockTelemetry, { _: LogLevel -> true })
-
- // When
- Logging.log(LogLevel.NONE, "none message")
-
- // Wait for async logging
- runBlocking {
- delay(100)
- }
-
- // Then - should not crash, NONE level is skipped
- }
-
- test("logToOtel should handle exceptions in logs") {
- // Given
- Logging.setOtelTelemetry(mockTelemetry, { _: LogLevel -> true })
- val exception = RuntimeException("test exception")
-
- // When
- Logging.error("error with exception", exception)
-
- // Wait for async logging
- runBlocking {
- delay(100)
- }
-
- // Then - should not crash, exception details are included
- }
-
- test("logToOtel should handle null exception message") {
- // Given
- Logging.setOtelTelemetry(mockTelemetry, { _: LogLevel -> true })
- val exception = RuntimeException()
-
- // When
- Logging.error("error with null exception message", exception)
-
- // Wait for async logging
- runBlocking {
- delay(100)
- }
-
- // Then - should not crash
- }
-
- test("logToOtel should handle Otel errors gracefully") {
- // Given
- Logging.setOtelTelemetry(mockTelemetry, { _: LogLevel -> true })
- // Note: We can't mock getLogger() to throw due to OpenTelemetry type visibility,
- // but the real implementation in Logging.logToOtel() handles errors gracefully
-
- // When
- Logging.info("test message")
-
- // Wait for async logging
- runBlocking {
- delay(100)
- }
-
- // Then - should not crash, error handling is tested in integration tests
- }
-
- test("logToOtel should use dynamic remote logging check") {
- // Given
- var isEnabled = false
- Logging.setOtelTelemetry(mockTelemetry, { _: LogLevel -> isEnabled })
-
- // When - initially disabled
- Logging.info("message 1")
- runBlocking { delay(50) }
-
- // When - enable remote logging
- isEnabled = true
- Logging.info("message 2")
- runBlocking { delay(50) }
-
- // When - disable again
- isEnabled = false
- Logging.info("message 3")
- runBlocking { delay(50) }
-
- // Then - should not crash, dynamic check works
- }
-
- test("logToOtel should handle multiple rapid log calls") {
- // Given
- Logging.setOtelTelemetry(mockTelemetry, { _: LogLevel -> true })
-
- // When - rapid fire logging
- repeat(10) {
- Logging.info("message $it")
- }
-
- // Wait for async logging
- runBlocking {
- delay(200)
- }
-
- // Then - should not crash
- }
-
- test("logToOtel should work with different message formats") {
- // Given
- Logging.setOtelTelemetry(mockTelemetry, { _: LogLevel -> true })
-
- // When
- Logging.info("simple message")
- Logging.info("message with numbers: 12345")
- Logging.info("message with special chars: !@#$%")
- Logging.info("message with unicode: 测试 🚀")
-
- // Wait for async logging
- runBlocking {
- delay(200)
- }
-
- // Then - should not crash
- }
-})
diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/logging/LoggingTest.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/logging/LoggingTest.kt
deleted file mode 100644
index 92d8d69885..0000000000
--- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/logging/LoggingTest.kt
+++ /dev/null
@@ -1,360 +0,0 @@
-package com.onesignal.debug.internal.logging
-
-import android.os.Build
-import br.com.colman.kotest.android.extensions.robolectric.RobolectricTest
-import com.onesignal.debug.ILogListener
-import com.onesignal.debug.LogLevel
-import com.onesignal.debug.OneSignalLogEvent
-import com.onesignal.otel.IOtelOpenTelemetryRemote
-import io.kotest.core.spec.style.FunSpec
-import io.kotest.matchers.shouldBe
-import io.mockk.every
-import io.mockk.mockk
-import io.mockk.slot
-import io.mockk.verify
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.runBlocking
-import org.robolectric.annotation.Config
-
-@RobolectricTest
-@Config(sdk = [Build.VERSION_CODES.O])
-class LoggingTest : FunSpec({
-
- val originalLogLevel = Logging.logLevel
- val originalVisualLogLevel = Logging.visualLogLevel
-
- beforeEach {
- // Reset Logging state
- Logging.logLevel = LogLevel.WARN
- Logging.visualLogLevel = LogLevel.NONE
- Logging.setOtelTelemetry(null) { false }
- }
-
- afterEach {
- // Restore original state
- Logging.logLevel = originalLogLevel
- Logging.visualLogLevel = originalVisualLogLevel
- Logging.setOtelTelemetry(null) { false }
- }
-
- // ===== Log Level Tests =====
-
- test("default logLevel should be WARN") {
- // Reset to default
- Logging.logLevel = LogLevel.WARN
-
- // Then
- Logging.logLevel shouldBe LogLevel.WARN
- }
-
- test("default visualLogLevel should be NONE") {
- // Reset to default
- Logging.visualLogLevel = LogLevel.NONE
-
- // Then
- Logging.visualLogLevel shouldBe LogLevel.NONE
- }
-
- test("logLevel can be changed") {
- // When
- Logging.logLevel = LogLevel.DEBUG
-
- // Then
- Logging.logLevel shouldBe LogLevel.DEBUG
- }
-
- test("visualLogLevel can be changed") {
- // When
- Logging.visualLogLevel = LogLevel.INFO
-
- // Then
- Logging.visualLogLevel shouldBe LogLevel.INFO
- }
-
- // ===== atLogLevel Tests =====
-
- test("atLogLevel returns true when level is at or below logLevel") {
- // Given
- Logging.logLevel = LogLevel.WARN
-
- // Then
- Logging.atLogLevel(LogLevel.WARN) shouldBe true
- Logging.atLogLevel(LogLevel.ERROR) shouldBe true
- Logging.atLogLevel(LogLevel.FATAL) shouldBe true
- }
-
- test("atLogLevel returns false when level is above logLevel") {
- // Given
- Logging.logLevel = LogLevel.WARN
- Logging.visualLogLevel = LogLevel.NONE
-
- // Then
- Logging.atLogLevel(LogLevel.INFO) shouldBe false
- Logging.atLogLevel(LogLevel.DEBUG) shouldBe false
- Logging.atLogLevel(LogLevel.VERBOSE) shouldBe false
- }
-
- test("atLogLevel considers visualLogLevel too") {
- // Given
- Logging.logLevel = LogLevel.NONE
- Logging.visualLogLevel = LogLevel.INFO
-
- // Then - INFO should pass because visualLogLevel is INFO
- Logging.atLogLevel(LogLevel.INFO) shouldBe true
- }
-
- // ===== Log Methods Tests =====
-
- test("verbose method should not throw") {
- // Given
- Logging.logLevel = LogLevel.VERBOSE
-
- // When & Then - should not throw
- Logging.verbose("Test message")
- Logging.verbose("Test message with throwable", RuntimeException("test"))
- }
-
- test("debug method should not throw") {
- // Given
- Logging.logLevel = LogLevel.DEBUG
-
- // When & Then - should not throw
- Logging.debug("Test message")
- Logging.debug("Test message with throwable", RuntimeException("test"))
- }
-
- test("info method should not throw") {
- // Given
- Logging.logLevel = LogLevel.INFO
-
- // When & Then - should not throw
- Logging.info("Test message")
- Logging.info("Test message with throwable", RuntimeException("test"))
- }
-
- test("warn method should not throw") {
- // Given
- Logging.logLevel = LogLevel.WARN
-
- // When & Then - should not throw
- Logging.warn("Test message")
- Logging.warn("Test message with throwable", RuntimeException("test"))
- }
-
- test("error method should not throw") {
- // Given
- Logging.logLevel = LogLevel.ERROR
-
- // When & Then - should not throw
- Logging.error("Test message")
- Logging.error("Test message with throwable", RuntimeException("test"))
- }
-
- test("fatal method should not throw") {
- // Given
- Logging.logLevel = LogLevel.FATAL
-
- // When & Then - should not throw
- Logging.fatal("Test message")
- Logging.fatal("Test message with throwable", RuntimeException("test"))
- }
-
- test("log method with level and message should not throw") {
- // When & Then - should not throw
- Logging.log(LogLevel.INFO, "Test message")
- }
-
- test("log method with level, message, and throwable should not throw") {
- // When & Then - should not throw
- Logging.log(LogLevel.ERROR, "Test message", RuntimeException("test"))
- }
-
- // ===== Log Listener Tests =====
-
- test("addListener should register listener") {
- // Given
- val mockListener = mockk(relaxed = true)
- val eventSlot = slot()
- every { mockListener.onLogEvent(capture(eventSlot)) } returns Unit
-
- Logging.addListener(mockListener)
- Logging.logLevel = LogLevel.INFO
-
- // When
- Logging.info("Test listener message")
-
- // Then
- verify { mockListener.onLogEvent(any()) }
- eventSlot.captured.level shouldBe LogLevel.INFO
- eventSlot.captured.entry.contains("Test listener message") shouldBe true
-
- // Cleanup
- Logging.removeListener(mockListener)
- }
-
- test("removeListener should unregister listener") {
- // Given
- val mockListener = mockk(relaxed = true)
- Logging.addListener(mockListener)
- Logging.removeListener(mockListener)
- Logging.logLevel = LogLevel.INFO
-
- // When
- Logging.info("Test message after removal")
-
- // Then - listener should not be called
- verify(exactly = 0) { mockListener.onLogEvent(any()) }
- }
-
- test("listener should receive throwable in message") {
- // Given
- val mockListener = mockk(relaxed = true)
- val eventSlot = slot()
- every { mockListener.onLogEvent(capture(eventSlot)) } returns Unit
-
- Logging.addListener(mockListener)
- Logging.logLevel = LogLevel.ERROR
-
- // When
- val exception = RuntimeException("Test exception message")
- Logging.error("Test error", exception)
-
- // Then
- verify { mockListener.onLogEvent(any()) }
- eventSlot.captured.entry.contains("Test error") shouldBe true
- eventSlot.captured.entry.contains("Test exception message") shouldBe true
-
- // Cleanup
- Logging.removeListener(mockListener)
- }
-
- // ===== Otel Integration Tests =====
-
- test("setOtelTelemetry should set telemetry instance") {
- // Given
- val mockTelemetry = mockk(relaxed = true)
-
- // When
- Logging.setOtelTelemetry(mockTelemetry) { true }
-
- // Then - no exception thrown
- }
-
- test("setOtelTelemetry with null should clear telemetry") {
- // Given
- val mockTelemetry = mockk(relaxed = true)
- Logging.setOtelTelemetry(mockTelemetry) { true }
-
- // When
- Logging.setOtelTelemetry(null) { false }
-
- // Then - no exception thrown
- }
-
- test("log with Otel configured should not throw") {
- // Given - Using relaxed mock that doesn't require OpenTelemetry classes
- val mockTelemetry = mockk(relaxed = true)
-
- Logging.setOtelTelemetry(mockTelemetry) { level -> level >= LogLevel.ERROR }
- Logging.logLevel = LogLevel.ERROR
-
- // When & Then - should not throw
- Logging.error("Test Otel error message")
- runBlocking { delay(100) }
- }
-
- test("log with Otel telemetry set to null should not throw") {
- // Given
- Logging.setOtelTelemetry(null) { true }
- Logging.logLevel = LogLevel.ERROR
-
- // When & Then - should not throw
- Logging.error("Test error - telemetry is null")
- }
-
- test("log with NONE level and Otel configured should not throw") {
- // Given
- val mockTelemetry = mockk(relaxed = true)
- Logging.setOtelTelemetry(mockTelemetry) { true }
-
- // When & Then - should not throw
- Logging.log(LogLevel.NONE, "Should not be logged")
- }
-
- // ===== Message Formatting Tests =====
-
- test("log message should include thread name") {
- // Given
- val mockListener = mockk(relaxed = true)
- val eventSlot = slot()
- every { mockListener.onLogEvent(capture(eventSlot)) } returns Unit
-
- Logging.addListener(mockListener)
- Logging.logLevel = LogLevel.INFO
-
- // When
- Logging.info("Test message")
-
- // Then - message should contain thread name in brackets
- eventSlot.captured.entry.contains("[") shouldBe true
- eventSlot.captured.entry.contains("]") shouldBe true
-
- // Cleanup
- Logging.removeListener(mockListener)
- }
-
- // ===== Thread Safety Tests =====
-
- test("multiple listeners can be added safely") {
- // Given
- val listener1 = mockk(relaxed = true)
- val listener2 = mockk(relaxed = true)
- val listener3 = mockk(relaxed = true)
-
- Logging.addListener(listener1)
- Logging.addListener(listener2)
- Logging.addListener(listener3)
- Logging.logLevel = LogLevel.INFO
-
- // When
- Logging.info("Test message to multiple listeners")
-
- // Then - all listeners should receive the event
- verify { listener1.onLogEvent(any()) }
- verify { listener2.onLogEvent(any()) }
- verify { listener3.onLogEvent(any()) }
-
- // Cleanup
- Logging.removeListener(listener1)
- Logging.removeListener(listener2)
- Logging.removeListener(listener3)
- }
-
- test("removing non-existent listener should not throw") {
- // Given
- val mockListener = mockk(relaxed = true)
-
- // When & Then - should not throw
- Logging.removeListener(mockListener)
- }
-
- // ===== All Log Levels Tests =====
-
- test("all log levels should work correctly") {
- // Given
- Logging.logLevel = LogLevel.VERBOSE
- val logLevels = listOf(
- LogLevel.VERBOSE to { msg: String -> Logging.verbose(msg) },
- LogLevel.DEBUG to { msg: String -> Logging.debug(msg) },
- LogLevel.INFO to { msg: String -> Logging.info(msg) },
- LogLevel.WARN to { msg: String -> Logging.warn(msg) },
- LogLevel.ERROR to { msg: String -> Logging.error(msg) },
- LogLevel.FATAL to { msg: String -> Logging.fatal(msg) }
- )
-
- // When & Then - none should throw
- logLevels.forEach { (level, logFn) ->
- logFn("Test message for level $level")
- }
- }
-})
diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/logging/otel/android/AndroidOtelLoggerTest.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/logging/otel/android/AndroidOtelLoggerTest.kt
deleted file mode 100644
index 67336bd367..0000000000
--- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/logging/otel/android/AndroidOtelLoggerTest.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-package com.onesignal.debug.internal.logging.otel.android
-
-import com.onesignal.debug.LogLevel
-import com.onesignal.debug.internal.logging.Logging
-import com.onesignal.otel.IOtelLogger
-import io.kotest.core.spec.style.FunSpec
-import io.kotest.matchers.types.shouldBeInstanceOf
-
-class AndroidOtelLoggerTest : FunSpec({
- // Save original log level to restore after tests
- val originalLogLevel = Logging.logLevel
-
- beforeEach {
- // Disable logging during tests to avoid polluting test output
- Logging.logLevel = LogLevel.NONE
- }
-
- afterEach {
- // Restore original log level
- Logging.logLevel = originalLogLevel
- }
-
- test("should implement IOtelLogger interface") {
- val logger = AndroidOtelLogger()
-
- logger.shouldBeInstanceOf()
- }
-
- test("error should not throw") {
- val logger = AndroidOtelLogger()
-
- // Should not throw
- logger.error("test error message")
- }
-
- test("warn should not throw") {
- val logger = AndroidOtelLogger()
-
- // Should not throw
- logger.warn("test warn message")
- }
-
- test("info should not throw") {
- val logger = AndroidOtelLogger()
-
- // Should not throw
- logger.info("test info message")
- }
-
- test("debug should not throw") {
- val logger = AndroidOtelLogger()
-
- // Should not throw
- logger.debug("test debug message")
- }
-
- test("should handle empty messages") {
- val logger = AndroidOtelLogger()
-
- // Should not throw with empty messages
- logger.error("")
- logger.warn("")
- logger.info("")
- logger.debug("")
- }
-
- test("should handle messages with special characters") {
- val logger = AndroidOtelLogger()
-
- // Should not throw with special characters
- logger.error("Error: \n\t special chars: @#$%^&*()")
- logger.info("Unicode: 日本語 中文 한국어")
- }
-})
diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/logging/otel/android/OtelIdResolverTest.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/logging/otel/android/OtelIdResolverTest.kt
deleted file mode 100644
index 86be0f189a..0000000000
--- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/logging/otel/android/OtelIdResolverTest.kt
+++ /dev/null
@@ -1,1051 +0,0 @@
-package com.onesignal.debug.internal.logging.otel.android
-
-import android.content.Context
-import android.content.SharedPreferences
-import androidx.test.core.app.ApplicationProvider
-import br.com.colman.kotest.android.extensions.robolectric.RobolectricTest
-import com.onesignal.common.IDManager.LOCAL_PREFIX
-import com.onesignal.core.internal.config.ConfigModel
-import com.onesignal.core.internal.preferences.PreferenceOneSignalKeys
-import com.onesignal.core.internal.preferences.PreferenceStores
-import com.onesignal.debug.LogLevel
-import com.onesignal.user.internal.backend.IdentityConstants
-import io.kotest.core.spec.style.FunSpec
-import io.kotest.matchers.shouldBe
-import io.mockk.every
-import io.mockk.mockk
-import org.json.JSONArray
-import org.json.JSONObject
-import com.onesignal.core.internal.config.CONFIG_NAME_SPACE as configNameSpace
-import com.onesignal.user.internal.identity.IDENTITY_NAME_SPACE as identityNameSpace
-
-@RobolectricTest
-class OtelIdResolverTest : FunSpec({
-
- var appContext: Context? = null
- var sharedPreferences: SharedPreferences? = null
-
- // Helper function to ensure SharedPreferences data is written and verified
- fun writeAndVerifyConfigData(configArray: JSONArray) {
- val editor = sharedPreferences!!.edit()
- editor.putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, configArray.toString())
- editor.commit()
-
- // Get a fresh SharedPreferences instance to ensure we read the latest data
- val freshPrefs = appContext!!.getSharedPreferences(PreferenceStores.ONESIGNAL, Context.MODE_PRIVATE)
- val verifyData = freshPrefs.getString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, null)
- if (verifyData == null || verifyData != configArray.toString()) {
- throw AssertionError("Failed to write SharedPreferences data - test isolation issue")
- }
- }
-
- // Helper function to ensure SharedPreferences identity data is written and verified
- fun writeAndVerifyIdentityData(identityArray: JSONArray) {
- val editor = sharedPreferences!!.edit()
- editor.putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + identityNameSpace, identityArray.toString())
- editor.commit()
-
- // Get a fresh SharedPreferences instance to ensure we read the latest data
- val freshPrefs = appContext!!.getSharedPreferences(PreferenceStores.ONESIGNAL, Context.MODE_PRIVATE)
- val verifyData = freshPrefs.getString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + identityNameSpace, null)
- if (verifyData == null || verifyData != identityArray.toString()) {
- throw AssertionError("Failed to write SharedPreferences data - test isolation issue")
- }
- }
-
- beforeAny {
- if (appContext == null) {
- appContext = ApplicationProvider.getApplicationContext()
- sharedPreferences = appContext!!.getSharedPreferences(PreferenceStores.ONESIGNAL, Context.MODE_PRIVATE)
- }
- // AGGRESSIVE CLEANUP: Clear ALL SharedPreferences before any test runs
- // This ensures clean state even if previous test classes left data behind
- sharedPreferences!!.edit().clear().commit()
- try {
- val otherPrefs = appContext!!.getSharedPreferences("com.onesignal", Context.MODE_PRIVATE)
- otherPrefs.edit().clear().commit()
- } catch (e: Exception) {
- // Ignore any errors during cleanup
- }
- }
-
- beforeEach {
- // Ensure appContext is initialized
- if (appContext == null) {
- appContext = ApplicationProvider.getApplicationContext()
- }
-
- // Get a FRESH SharedPreferences instance for each test to avoid caching issues
- // This ensures we're not reading stale data from previous tests
- sharedPreferences = appContext!!.getSharedPreferences(PreferenceStores.ONESIGNAL, Context.MODE_PRIVATE)
-
- // AGGRESSIVE CLEANUP: Clear ALL SharedPreferences to ensure complete isolation
- sharedPreferences!!.edit().clear().commit()
-
- // Also clear any other potential SharedPreferences files
- try {
- val otherPrefs = appContext!!.getSharedPreferences("com.onesignal", Context.MODE_PRIVATE)
- otherPrefs.edit().clear().commit()
- } catch (e: Exception) {
- // Ignore any errors during cleanup
- }
- }
-
- afterEach {
- // Clean up after each test
- sharedPreferences!!.edit().clear().commit()
-
- // Also clear any other potential SharedPreferences files
- try {
- val otherPrefs = appContext!!.getSharedPreferences("com.onesignal", Context.MODE_PRIVATE)
- otherPrefs.edit().clear().commit()
- } catch (e: Exception) {
- // Ignore any errors during cleanup
- }
- }
-
- afterSpec {
- // Final cleanup after all tests in this spec
- if (appContext != null) {
- try {
- val prefs = appContext!!.getSharedPreferences(PreferenceStores.ONESIGNAL, Context.MODE_PRIVATE)
- prefs.edit().clear().commit()
-
- val otherPrefs = appContext!!.getSharedPreferences("com.onesignal", Context.MODE_PRIVATE)
- otherPrefs.edit().clear().commit()
- } catch (e: Exception) {
- // Ignore any errors during cleanup
- }
- }
- }
-
- // ===== resolveAppId Tests =====
-
- test("resolveAppId returns appId from ConfigModelStore when available") {
- // Given
- val configModel = JSONObject().apply {
- put(ConfigModel::appId.name, "test-app-id-123")
- }
- val configArray = JSONArray().apply {
- put(configModel)
- }
- // Write data and ensure it's committed
- val editor = sharedPreferences!!.edit()
- editor.putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, configArray.toString())
- editor.commit() // Use commit() to ensure synchronous write
-
- // Get a fresh SharedPreferences instance to ensure we read the latest data
- val freshPrefs = appContext!!.getSharedPreferences(PreferenceStores.ONESIGNAL, Context.MODE_PRIVATE)
- val verifyData = freshPrefs.getString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, null)
- if (verifyData == null || verifyData != configArray.toString()) {
- throw AssertionError("Failed to write SharedPreferences data - test isolation issue")
- }
-
- val resolver = OtelIdResolver(appContext!!)
-
- // When
- val result = resolver.resolveAppId()
-
- // Then
- result shouldBe "test-app-id-123"
- }
-
- test("resolveAppId returns empty string appId as null and falls back to legacy") {
- // Given
- val configModel = JSONObject().apply {
- put(ConfigModel::appId.name, "")
- }
- val configArray = JSONArray().apply {
- put(configModel)
- }
- sharedPreferences!!.edit()
- .putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, configArray.toString())
- .putString(PreferenceOneSignalKeys.PREFS_LEGACY_APP_ID, "legacy-app-id")
- .commit()
-
- // Ensure commit is complete before creating resolver
- Thread.sleep(10)
-
- val resolver = OtelIdResolver(appContext!!)
-
- // When
- val result = resolver.resolveAppId()
-
- // Then
- result shouldBe "legacy-app-id"
- }
-
- test("resolveAppId falls back to legacy SharedPreferences when ConfigModelStore has no appId") {
- // Given
- val configModel = JSONObject() // No appId field
- val configArray = JSONArray().apply {
- put(configModel)
- }
- sharedPreferences!!.edit()
- .putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, configArray.toString())
- .putString(PreferenceOneSignalKeys.PREFS_LEGACY_APP_ID, "legacy-app-id")
- .commit()
-
- // Ensure commit is complete before creating resolver
- Thread.sleep(10)
-
- val resolver = OtelIdResolver(appContext!!)
-
- // When
- val result = resolver.resolveAppId()
-
- // Then
- result shouldBe "legacy-app-id"
- }
-
- test("resolveAppId returns error appId when ConfigModelStore is null") {
- // Given
- val resolver = OtelIdResolver(appContext!!)
-
- // When
- val result = resolver.resolveAppId()
-
- // Then - ERROR_APP_ID_PREFIX_NO_CONFIG_STORE
- result shouldBe "e1100000-0000-4000-a000-000000000002"
- }
-
- test("resolveAppId returns error appId when ConfigModelStore is empty array") {
- // Given
- val configArray = JSONArray() // Empty array
- // Write data and ensure it's committed
- val editor = sharedPreferences!!.edit()
- editor.putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, configArray.toString())
- editor.commit() // Use commit() to ensure synchronous write
-
- // Get a fresh SharedPreferences instance to ensure we read the latest data
- val freshPrefs = appContext!!.getSharedPreferences(PreferenceStores.ONESIGNAL, Context.MODE_PRIVATE)
- val verifyData = freshPrefs.getString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, null)
- if (verifyData == null || verifyData != configArray.toString()) {
- throw AssertionError("Failed to write SharedPreferences data - test isolation issue")
- }
-
- val resolver = OtelIdResolver(appContext!!)
-
- // When
- val result = resolver.resolveAppId()
-
- // Then - ERROR_APP_ID_PREFIX_NO_APPID_IN_CONFIG_STORE
- result shouldBe "e1100000-0000-4000-a000-000000000003"
- }
-
- test("resolveAppId returns error appId when context is null") {
- // Given
- val resolver = OtelIdResolver(null)
-
- // When
- val result = resolver.resolveAppId()
-
- // Then - ERROR_APP_ID_PREFIX_NO_CONTEXT
- result shouldBe "e1100000-0000-4000-a000-000000000004"
- }
-
- test("resolveAppId handles JSON parsing exceptions gracefully") {
- // Given
- sharedPreferences!!.edit()
- .putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, "invalid-json")
- .commit()
-
- val resolver = OtelIdResolver(appContext!!)
-
- // When
- val result = resolver.resolveAppId()
-
- // Then - JSON parse error results in null configModel, so ERROR_APP_ID_PREFIX_NO_CONFIG_STORE
- result shouldBe "e1100000-0000-4000-a000-000000000002"
- }
-
- // ===== resolveOnesignalId Tests =====
-
- test("resolveOnesignalId returns onesignalId from IdentityModelStore when available") {
- // Given
- val identityModel = JSONObject().apply {
- put(IdentityConstants.ONESIGNAL_ID, "test-onesignal-id-123")
- }
- val identityArray = JSONArray().apply {
- put(identityModel)
- }
- // Write data and ensure it's committed
- val editor = sharedPreferences!!.edit()
- editor.putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + identityNameSpace, identityArray.toString())
- editor.commit() // Use commit() to ensure synchronous write
-
- // Get a fresh SharedPreferences instance to ensure we read the latest data
- val freshPrefs = appContext!!.getSharedPreferences(PreferenceStores.ONESIGNAL, Context.MODE_PRIVATE)
- val verifyData = freshPrefs.getString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + identityNameSpace, null)
- if (verifyData == null || verifyData != identityArray.toString()) {
- throw AssertionError("Failed to write SharedPreferences data - test isolation issue")
- }
-
- val resolver = OtelIdResolver(appContext!!)
-
- // When
- val result = resolver.resolveOnesignalId()
-
- // Then
- result shouldBe "test-onesignal-id-123"
- }
-
- test("resolveOnesignalId returns null when onesignalId is empty string") {
- // Given
- val identityModel = JSONObject().apply {
- put(IdentityConstants.ONESIGNAL_ID, "")
- }
- val identityArray = JSONArray().apply {
- put(identityModel)
- }
- // Write data and ensure it's committed
- val editor = sharedPreferences!!.edit()
- editor.putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + identityNameSpace, identityArray.toString())
- editor.commit() // Use commit() to ensure synchronous write
-
- // Get a fresh SharedPreferences instance to ensure we read the latest data
- val freshPrefs = appContext!!.getSharedPreferences(PreferenceStores.ONESIGNAL, Context.MODE_PRIVATE)
- val verifyData = freshPrefs.getString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + identityNameSpace, null)
- if (verifyData == null || verifyData != identityArray.toString()) {
- throw AssertionError("Failed to write SharedPreferences data - test isolation issue")
- }
-
- val resolver = OtelIdResolver(appContext!!)
-
- // When
- val result = resolver.resolveOnesignalId()
-
- // Then
- result shouldBe null
- }
-
- test("resolveOnesignalId returns null when onesignalId is a local ID") {
- // Given
- val localId = "${LOCAL_PREFIX}test-id"
- val identityModel = JSONObject().apply {
- put(IdentityConstants.ONESIGNAL_ID, localId)
- }
- val identityArray = JSONArray().apply {
- put(identityModel)
- }
- // Write data and ensure it's committed
- val editor = sharedPreferences!!.edit()
- editor.putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + identityNameSpace, identityArray.toString())
- editor.commit() // Use commit() to ensure synchronous write
-
- // Get a fresh SharedPreferences instance to ensure we read the latest data
- val freshPrefs = appContext!!.getSharedPreferences(PreferenceStores.ONESIGNAL, Context.MODE_PRIVATE)
- val verifyData = freshPrefs.getString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + identityNameSpace, null)
- if (verifyData == null || verifyData != identityArray.toString()) {
- throw AssertionError("Failed to write SharedPreferences data - test isolation issue")
- }
-
- val resolver = OtelIdResolver(appContext!!)
-
- // When
- val result = resolver.resolveOnesignalId()
-
- // Then
- result shouldBe null
- }
-
- test("resolveOnesignalId returns null when IdentityModelStore has no onesignalId field") {
- // Given
- val identityModel = JSONObject() // No ONESIGNAL_ID field
- val identityArray = JSONArray().apply {
- put(identityModel)
- }
- // Write data and ensure it's committed
- val editor = sharedPreferences!!.edit()
- editor.putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + identityNameSpace, identityArray.toString())
- editor.commit() // Use commit() to ensure synchronous write
-
- // Get a fresh SharedPreferences instance to ensure we read the latest data
- val freshPrefs = appContext!!.getSharedPreferences(PreferenceStores.ONESIGNAL, Context.MODE_PRIVATE)
- val verifyData = freshPrefs.getString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + identityNameSpace, null)
- if (verifyData == null || verifyData != identityArray.toString()) {
- throw AssertionError("Failed to write SharedPreferences data - test isolation issue")
- }
-
- val resolver = OtelIdResolver(appContext!!)
-
- // When
- val result = resolver.resolveOnesignalId()
-
- // Then
- result shouldBe null
- }
-
- test("resolveOnesignalId returns null when IdentityModelStore is null") {
- // Given
- val resolver = OtelIdResolver(appContext!!)
-
- // When
- val result = resolver.resolveOnesignalId()
-
- // Then
- result shouldBe null
- }
-
- test("resolveOnesignalId returns null when IdentityModelStore is empty array") {
- // Given
- val identityArray = JSONArray() // Empty array
- // Write data and ensure it's committed
- val editor = sharedPreferences!!.edit()
- editor.putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + identityNameSpace, identityArray.toString())
- editor.commit() // Use commit() to ensure synchronous write
-
- // Get a fresh SharedPreferences instance to ensure we read the latest data
- val freshPrefs = appContext!!.getSharedPreferences(PreferenceStores.ONESIGNAL, Context.MODE_PRIVATE)
- val verifyData = freshPrefs.getString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + identityNameSpace, null)
- if (verifyData == null || verifyData != identityArray.toString()) {
- throw AssertionError("Failed to write SharedPreferences data - test isolation issue")
- }
-
- val resolver = OtelIdResolver(appContext!!)
-
- // When
- val result = resolver.resolveOnesignalId()
-
- // Then
- result shouldBe null
- }
-
- test("resolveOnesignalId handles JSON parsing exceptions gracefully") {
- // Given
- sharedPreferences!!.edit()
- .putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + identityNameSpace, "invalid-json")
- .commit()
-
- val resolver = OtelIdResolver(appContext!!)
-
- // When
- val result = resolver.resolveOnesignalId()
-
- // Then
- result shouldBe null
- }
-
- // ===== resolvePushSubscriptionId Tests =====
-
- test("resolvePushSubscriptionId returns pushSubscriptionId from ConfigModelStore when available") {
- // Given
- val configModel = JSONObject().apply {
- put(ConfigModel::pushSubscriptionId.name, "test-push-sub-id-123")
- }
- val configArray = JSONArray().apply {
- put(configModel)
- }
- // Write data and ensure it's committed
- val editor = sharedPreferences!!.edit()
- editor.putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, configArray.toString())
- editor.commit() // Use commit() to ensure synchronous write
-
- // Get a fresh SharedPreferences instance to ensure we read the latest data
- val freshPrefs = appContext!!.getSharedPreferences(PreferenceStores.ONESIGNAL, Context.MODE_PRIVATE)
- val verifyData = freshPrefs.getString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, null)
- if (verifyData == null || verifyData != configArray.toString()) {
- throw AssertionError("Failed to write SharedPreferences data - test isolation issue")
- }
-
- val resolver = OtelIdResolver(appContext!!)
-
- // When
- val result = resolver.resolvePushSubscriptionId()
-
- // Then
- result shouldBe "test-push-sub-id-123"
- }
-
- test("resolvePushSubscriptionId returns null when pushSubscriptionId is empty string") {
- // Given
- val configModel = JSONObject().apply {
- put(ConfigModel::pushSubscriptionId.name, "")
- }
- val configArray = JSONArray().apply {
- put(configModel)
- }
- // Write data and ensure it's committed
- val editor = sharedPreferences!!.edit()
- editor.putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, configArray.toString())
- editor.commit() // Use commit() to ensure synchronous write
-
- // Get a fresh SharedPreferences instance to ensure we read the latest data
- val freshPrefs = appContext!!.getSharedPreferences(PreferenceStores.ONESIGNAL, Context.MODE_PRIVATE)
- val verifyData = freshPrefs.getString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, null)
- if (verifyData == null || verifyData != configArray.toString()) {
- throw AssertionError("Failed to write SharedPreferences data - test isolation issue")
- }
-
- val resolver = OtelIdResolver(appContext!!)
-
- // When
- val result = resolver.resolvePushSubscriptionId()
-
- // Then
- result shouldBe null
- }
-
- test("resolvePushSubscriptionId returns null when pushSubscriptionId is a local ID") {
- // Given
- val localId = "${LOCAL_PREFIX}test-id"
- val configModel = JSONObject().apply {
- put(ConfigModel::pushSubscriptionId.name, localId)
- }
- val configArray = JSONArray().apply {
- put(configModel)
- }
- // Write data and ensure it's committed
- val editor = sharedPreferences!!.edit()
- editor.putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, configArray.toString())
- editor.commit() // Use commit() to ensure synchronous write
-
- // Get a fresh SharedPreferences instance to ensure we read the latest data
- val freshPrefs = appContext!!.getSharedPreferences(PreferenceStores.ONESIGNAL, Context.MODE_PRIVATE)
- val verifyData = freshPrefs.getString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, null)
- if (verifyData == null || verifyData != configArray.toString()) {
- throw AssertionError("Failed to write SharedPreferences data - test isolation issue")
- }
-
- val resolver = OtelIdResolver(appContext!!)
-
- // When
- val result = resolver.resolvePushSubscriptionId()
-
- // Then
- result shouldBe null
- }
-
- test("resolvePushSubscriptionId returns null when ConfigModelStore has no pushSubscriptionId field") {
- // Given
- val configModel = JSONObject() // No pushSubscriptionId field
- val configArray = JSONArray().apply {
- put(configModel)
- }
- // Write data and ensure it's committed
- val editor = sharedPreferences!!.edit()
- editor.putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, configArray.toString())
- editor.commit() // Use commit() to ensure synchronous write
-
- // Get a fresh SharedPreferences instance to ensure we read the latest data
- val freshPrefs = appContext!!.getSharedPreferences(PreferenceStores.ONESIGNAL, Context.MODE_PRIVATE)
- val verifyData = freshPrefs.getString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, null)
- if (verifyData == null || verifyData != configArray.toString()) {
- throw AssertionError("Failed to write SharedPreferences data - test isolation issue")
- }
-
- val resolver = OtelIdResolver(appContext!!)
-
- // When
- val result = resolver.resolvePushSubscriptionId()
-
- // Then
- result shouldBe null
- }
-
- test("resolvePushSubscriptionId returns null when ConfigModelStore is null") {
- // Given
- val resolver = OtelIdResolver(appContext!!)
-
- // When
- val result = resolver.resolvePushSubscriptionId()
-
- // Then
- result shouldBe null
- }
-
- test("resolvePushSubscriptionId handles JSON parsing exceptions gracefully") {
- // Given
- sharedPreferences!!.edit()
- .putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, "invalid-json")
- .commit()
-
- val resolver = OtelIdResolver(appContext!!)
-
- // When
- val result = resolver.resolvePushSubscriptionId()
-
- // Then
- result shouldBe null
- }
-
- // ===== resolveRemoteLoggingEnabled Tests =====
- // Enabled is derived from presence of a valid logLevel:
- // "logging_config": {} → disabled (not on allowlist)
- // "logging_config": {"log_level": "ERROR"} → enabled (on allowlist)
-
- test("resolveRemoteLoggingEnabled returns true when logLevel is ERROR") {
- val remoteLoggingParams = JSONObject().apply {
- put("logLevel", "ERROR")
- }
- val configModel = JSONObject().apply {
- put(ConfigModel::remoteLoggingParams.name, remoteLoggingParams)
- }
- val configArray = JSONArray().apply {
- put(configModel)
- }
- sharedPreferences!!.edit()
- .putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, configArray.toString())
- .commit()
-
- val resolver = OtelIdResolver(appContext!!)
- resolver.resolveRemoteLoggingEnabled() shouldBe true
- }
-
- test("resolveRemoteLoggingEnabled returns true when logLevel is WARN") {
- val remoteLoggingParams = JSONObject().apply {
- put("logLevel", "WARN")
- }
- val configModel = JSONObject().apply {
- put(ConfigModel::remoteLoggingParams.name, remoteLoggingParams)
- }
- val configArray = JSONArray().apply {
- put(configModel)
- }
- sharedPreferences!!.edit()
- .putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, configArray.toString())
- .commit()
-
- val resolver = OtelIdResolver(appContext!!)
- resolver.resolveRemoteLoggingEnabled() shouldBe true
- }
-
- test("resolveRemoteLoggingEnabled returns false when logLevel is NONE") {
- val remoteLoggingParams = JSONObject().apply {
- put("logLevel", "NONE")
- }
- val configModel = JSONObject().apply {
- put(ConfigModel::remoteLoggingParams.name, remoteLoggingParams)
- }
- val configArray = JSONArray().apply {
- put(configModel)
- }
- sharedPreferences!!.edit()
- .putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, configArray.toString())
- .commit()
-
- val resolver = OtelIdResolver(appContext!!)
- resolver.resolveRemoteLoggingEnabled() shouldBe false
- }
-
- test("resolveRemoteLoggingEnabled returns false when logLevel field is missing (empty logging_config)") {
- val remoteLoggingParams = JSONObject()
- val configModel = JSONObject().apply {
- put(ConfigModel::remoteLoggingParams.name, remoteLoggingParams)
- }
- val configArray = JSONArray().apply {
- put(configModel)
- }
- sharedPreferences!!.edit()
- .putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, configArray.toString())
- .commit()
-
- val resolver = OtelIdResolver(appContext!!)
- resolver.resolveRemoteLoggingEnabled() shouldBe false
- }
-
- test("resolveRemoteLoggingEnabled returns false when remoteLoggingParams is missing") {
- val configModel = JSONObject()
- val configArray = JSONArray().apply {
- put(configModel)
- }
- sharedPreferences!!.edit()
- .putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, configArray.toString())
- .commit()
-
- val resolver = OtelIdResolver(appContext!!)
- resolver.resolveRemoteLoggingEnabled() shouldBe false
- }
-
- test("resolveRemoteLoggingEnabled returns false when no config exists") {
- val resolver = OtelIdResolver(appContext!!)
- resolver.resolveRemoteLoggingEnabled() shouldBe false
- }
-
- test("resolveRemoteLoggingEnabled returns false when logLevel is invalid") {
- val remoteLoggingParams = JSONObject().apply {
- put("logLevel", "INVALID_LEVEL")
- }
- val configModel = JSONObject().apply {
- put(ConfigModel::remoteLoggingParams.name, remoteLoggingParams)
- }
- val configArray = JSONArray().apply {
- put(configModel)
- }
- sharedPreferences!!.edit()
- .putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, configArray.toString())
- .commit()
-
- val resolver = OtelIdResolver(appContext!!)
- resolver.resolveRemoteLoggingEnabled() shouldBe false
- }
-
- // ===== resolveRemoteLogLevel Tests =====
-
- test("resolveRemoteLogLevel returns LogLevel from ConfigModelStore when available") {
- // Given
- val remoteLoggingParams = JSONObject().apply {
- put("logLevel", "ERROR")
- }
- val configModel = JSONObject().apply {
- put(ConfigModel::remoteLoggingParams.name, remoteLoggingParams)
- }
- val configArray = JSONArray().apply {
- put(configModel)
- }
- // Write data and ensure it's committed
- val editor = sharedPreferences!!.edit()
- editor.putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, configArray.toString())
- editor.commit() // Use commit() to ensure synchronous write
-
- // Get a fresh SharedPreferences instance to ensure we read the latest data
- val freshPrefs = appContext!!.getSharedPreferences(PreferenceStores.ONESIGNAL, Context.MODE_PRIVATE)
- val verifyData = freshPrefs.getString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, null)
- if (verifyData == null || verifyData != configArray.toString()) {
- throw AssertionError("Failed to write SharedPreferences data - test isolation issue")
- }
-
- val resolver = OtelIdResolver(appContext!!)
-
- // When
- val result = resolver.resolveRemoteLogLevel()
-
- // Then
- result shouldBe LogLevel.ERROR
- }
-
- test("resolveRemoteLogLevel returns LogLevel case-insensitively") {
- // Given
- val remoteLoggingParams = JSONObject().apply {
- put("logLevel", "warn")
- }
- val configModel = JSONObject().apply {
- put(ConfigModel::remoteLoggingParams.name, remoteLoggingParams)
- }
- val configArray = JSONArray().apply {
- put(configModel)
- }
- // Write data and ensure it's committed
- val editor = sharedPreferences!!.edit()
- editor.putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, configArray.toString())
- editor.commit() // Use commit() to ensure synchronous write
-
- // Get a fresh SharedPreferences instance to ensure we read the latest data
- val freshPrefs = appContext!!.getSharedPreferences(PreferenceStores.ONESIGNAL, Context.MODE_PRIVATE)
- val verifyData = freshPrefs.getString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, null)
- if (verifyData == null || verifyData != configArray.toString()) {
- throw AssertionError("Failed to write SharedPreferences data - test isolation issue")
- }
-
- val resolver = OtelIdResolver(appContext!!)
-
- // When
- val result = resolver.resolveRemoteLogLevel()
-
- // Then
- result shouldBe LogLevel.WARN
- }
-
- test("resolveRemoteLogLevel returns null when logLevel field is missing") {
- // Given
- val remoteLoggingParams = JSONObject() // No logLevel field
- val configModel = JSONObject().apply {
- put(ConfigModel::remoteLoggingParams.name, remoteLoggingParams)
- }
- val configArray = JSONArray().apply {
- put(configModel)
- }
- // Write data and ensure it's committed
- val editor = sharedPreferences!!.edit()
- editor.putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, configArray.toString())
- editor.commit() // Use commit() to ensure synchronous write
-
- // Get a fresh SharedPreferences instance to ensure we read the latest data
- val freshPrefs = appContext!!.getSharedPreferences(PreferenceStores.ONESIGNAL, Context.MODE_PRIVATE)
- val verifyData = freshPrefs.getString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, null)
- if (verifyData == null || verifyData != configArray.toString()) {
- throw AssertionError("Failed to write SharedPreferences data - test isolation issue")
- }
-
- val resolver = OtelIdResolver(appContext!!)
-
- // When
- val result = resolver.resolveRemoteLogLevel()
-
- // Then
- result shouldBe null
- }
-
- test("resolveRemoteLogLevel returns null when remoteLoggingParams field is missing") {
- // Given
- val configModel = JSONObject() // No remoteLoggingParams field
- val configArray = JSONArray().apply {
- put(configModel)
- }
- // Write data and ensure it's committed
- val editor = sharedPreferences!!.edit()
- editor.putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, configArray.toString())
- editor.commit() // Use commit() to ensure synchronous write
-
- // Get a fresh SharedPreferences instance to ensure we read the latest data
- val freshPrefs = appContext!!.getSharedPreferences(PreferenceStores.ONESIGNAL, Context.MODE_PRIVATE)
- val verifyData = freshPrefs.getString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, null)
- if (verifyData == null || verifyData != configArray.toString()) {
- throw AssertionError("Failed to write SharedPreferences data - test isolation issue")
- }
-
- val resolver = OtelIdResolver(appContext!!)
-
- // When
- val result = resolver.resolveRemoteLogLevel()
-
- // Then
- result shouldBe null
- }
-
- test("resolveRemoteLogLevel returns null when logLevel is invalid") {
- // Given
- val remoteLoggingParams = JSONObject().apply {
- put("logLevel", "INVALID_LEVEL")
- }
- val configModel = JSONObject().apply {
- put(ConfigModel::remoteLoggingParams.name, remoteLoggingParams)
- }
- val configArray = JSONArray().apply {
- put(configModel)
- }
- // Write data and ensure it's committed
- val editor = sharedPreferences!!.edit()
- editor.putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, configArray.toString())
- editor.commit() // Use commit() to ensure synchronous write
-
- // Get a fresh SharedPreferences instance to ensure we read the latest data
- val freshPrefs = appContext!!.getSharedPreferences(PreferenceStores.ONESIGNAL, Context.MODE_PRIVATE)
- val verifyData = freshPrefs.getString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, null)
- if (verifyData == null || verifyData != configArray.toString()) {
- throw AssertionError("Failed to write SharedPreferences data - test isolation issue")
- }
-
- val resolver = OtelIdResolver(appContext!!)
-
- // When
- val result = resolver.resolveRemoteLogLevel()
-
- // Then
- result shouldBe null
- }
-
- test("resolveRemoteLogLevel returns null when ConfigModelStore is null") {
- // Given
- val resolver = OtelIdResolver(appContext!!)
-
- // When
- val result = resolver.resolveRemoteLogLevel()
-
- // Then
- result shouldBe null
- }
-
- test("resolveRemoteLogLevel handles JSON parsing exceptions gracefully") {
- // Given
- sharedPreferences!!.edit()
- .putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, "invalid-json")
- .commit()
-
- val resolver = OtelIdResolver(appContext!!)
-
- // When
- val result = resolver.resolveRemoteLogLevel()
-
- // Then
- result shouldBe null
- }
-
- // ===== extractLogLevelFromParams Tests (via resolveRemoteLogLevel / resolveRemoteLoggingEnabled) =====
- // These test the exact JSON shapes received from the backend.
-
- test("extractLogLevelFromParams: {logLevel:NONE, isEnabled:false} returns NONE and disabled") {
- val remoteLoggingParams = JSONObject("""{"logLevel":"NONE","isEnabled":false}""")
- val configModel = JSONObject().apply {
- put(ConfigModel::remoteLoggingParams.name, remoteLoggingParams)
- }
- val configArray = JSONArray().apply { put(configModel) }
- writeAndVerifyConfigData(configArray)
-
- val resolver = OtelIdResolver(appContext!!)
- resolver.resolveRemoteLogLevel() shouldBe LogLevel.NONE
- resolver.resolveRemoteLoggingEnabled() shouldBe false
- }
-
- test("extractLogLevelFromParams: {logLevel:ERROR, isEnabled:true} returns ERROR and enabled") {
- val remoteLoggingParams = JSONObject("""{"logLevel":"ERROR","isEnabled":true}""")
- val configModel = JSONObject().apply {
- put(ConfigModel::remoteLoggingParams.name, remoteLoggingParams)
- }
- val configArray = JSONArray().apply { put(configModel) }
- writeAndVerifyConfigData(configArray)
-
- val resolver = OtelIdResolver(appContext!!)
- resolver.resolveRemoteLogLevel() shouldBe LogLevel.ERROR
- resolver.resolveRemoteLoggingEnabled() shouldBe true
- }
-
- test("extractLogLevelFromParams: {isEnabled:false} with no logLevel returns null and disabled") {
- val remoteLoggingParams = JSONObject("""{"isEnabled":false}""")
- val configModel = JSONObject().apply {
- put(ConfigModel::remoteLoggingParams.name, remoteLoggingParams)
- }
- val configArray = JSONArray().apply { put(configModel) }
- writeAndVerifyConfigData(configArray)
-
- val resolver = OtelIdResolver(appContext!!)
- resolver.resolveRemoteLogLevel() shouldBe null
- resolver.resolveRemoteLoggingEnabled() shouldBe false
- }
-
- test("extractLogLevelFromParams: empty object {} returns null and disabled") {
- val remoteLoggingParams = JSONObject("""{}""")
- val configModel = JSONObject().apply {
- put(ConfigModel::remoteLoggingParams.name, remoteLoggingParams)
- }
- val configArray = JSONArray().apply { put(configModel) }
- writeAndVerifyConfigData(configArray)
-
- val resolver = OtelIdResolver(appContext!!)
- resolver.resolveRemoteLogLevel() shouldBe null
- resolver.resolveRemoteLoggingEnabled() shouldBe false
- }
-
- test("extractLogLevelFromParams: {logLevel:WARN} without isEnabled returns WARN and enabled") {
- val remoteLoggingParams = JSONObject("""{"logLevel":"WARN"}""")
- val configModel = JSONObject().apply {
- put(ConfigModel::remoteLoggingParams.name, remoteLoggingParams)
- }
- val configArray = JSONArray().apply { put(configModel) }
- writeAndVerifyConfigData(configArray)
-
- val resolver = OtelIdResolver(appContext!!)
- resolver.resolveRemoteLogLevel() shouldBe LogLevel.WARN
- resolver.resolveRemoteLoggingEnabled() shouldBe true
- }
-
- test("extractLogLevelFromParams: {logLevel:error} lowercase returns ERROR (case-insensitive)") {
- val remoteLoggingParams = JSONObject("""{"logLevel":"error","isEnabled":true}""")
- val configModel = JSONObject().apply {
- put(ConfigModel::remoteLoggingParams.name, remoteLoggingParams)
- }
- val configArray = JSONArray().apply { put(configModel) }
- writeAndVerifyConfigData(configArray)
-
- val resolver = OtelIdResolver(appContext!!)
- resolver.resolveRemoteLogLevel() shouldBe LogLevel.ERROR
- resolver.resolveRemoteLoggingEnabled() shouldBe true
- }
-
- test("extractLogLevelFromParams: {logLevel:INVALID} returns null and disabled") {
- val remoteLoggingParams = JSONObject("""{"logLevel":"INVALID","isEnabled":true}""")
- val configModel = JSONObject().apply {
- put(ConfigModel::remoteLoggingParams.name, remoteLoggingParams)
- }
- val configArray = JSONArray().apply { put(configModel) }
- writeAndVerifyConfigData(configArray)
-
- val resolver = OtelIdResolver(appContext!!)
- resolver.resolveRemoteLogLevel() shouldBe null
- resolver.resolveRemoteLoggingEnabled() shouldBe false
- }
-
- test("extractLogLevelFromParams: isEnabled field does not influence logLevel resolution") {
- val remoteLoggingParams = JSONObject("""{"logLevel":"ERROR","isEnabled":false}""")
- val configModel = JSONObject().apply {
- put(ConfigModel::remoteLoggingParams.name, remoteLoggingParams)
- }
- val configArray = JSONArray().apply { put(configModel) }
- writeAndVerifyConfigData(configArray)
-
- val resolver = OtelIdResolver(appContext!!)
- resolver.resolveRemoteLogLevel() shouldBe LogLevel.ERROR
- resolver.resolveRemoteLoggingEnabled() shouldBe true
- }
-
- // ===== resolveInstallId Tests =====
-
- test("resolveInstallId returns installId from SharedPreferences when available") {
- // Given
- sharedPreferences!!.edit()
- .putString(PreferenceOneSignalKeys.PREFS_OS_INSTALL_ID, "test-install-id-123")
- .commit()
-
- val resolver = OtelIdResolver(appContext!!)
-
- // When
- val result = resolver.resolveInstallId()
-
- // Then
- result shouldBe "test-install-id-123"
- }
-
- test("resolveInstallId returns default InstallId-Null when not found") {
- // Given
- val resolver = OtelIdResolver(appContext!!)
-
- // When
- val result = resolver.resolveInstallId()
-
- // Then
- result shouldBe "InstallId-Null"
- }
-
- test("resolveInstallId returns InstallId-NotFound when exception occurs") {
- // Given
- val mockContext = mockk(relaxed = true)
- val mockSharedPreferences = mockk(relaxed = true)
- every { mockContext.getSharedPreferences(any(), any()) } throws RuntimeException("Test exception")
-
- val resolver = OtelIdResolver(mockContext)
-
- // When
- val result = resolver.resolveInstallId()
-
- // Then
- result shouldBe "InstallId-NotFound"
- }
-
- // ===== Caching Tests =====
-
- test("cachedConfigModel is reused across multiple resolve calls") {
- // Given
- val configModel = JSONObject().apply {
- put(ConfigModel::appId.name, "test-app-id")
- put(ConfigModel::pushSubscriptionId.name, "test-push-id")
- }
- val configArray = JSONArray().apply {
- put(configModel)
- }
- // Write data and ensure it's committed
- val editor = sharedPreferences!!.edit()
- editor.putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, configArray.toString())
- editor.commit() // Use commit() to ensure synchronous write
-
- // Get a fresh SharedPreferences instance to ensure we read the latest data
- val freshPrefs = appContext!!.getSharedPreferences(PreferenceStores.ONESIGNAL, Context.MODE_PRIVATE)
- val verifyData = freshPrefs.getString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, null)
- if (verifyData == null || verifyData != configArray.toString()) {
- throw AssertionError("Failed to write SharedPreferences data - test isolation issue")
- }
-
- val resolver = OtelIdResolver(appContext!!)
-
- // When - resolve multiple IDs
- val appId1 = resolver.resolveAppId()
- val pushId1 = resolver.resolvePushSubscriptionId()
- val appId2 = resolver.resolveAppId()
- val pushId2 = resolver.resolvePushSubscriptionId()
-
- // Then - should return same values (cached)
- appId1 shouldBe "test-app-id"
- pushId1 shouldBe "test-push-id"
- appId2 shouldBe "test-app-id"
- pushId2 shouldBe "test-push-id"
- }
-})
diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/logging/otel/android/OtelPlatformProviderTest.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/logging/otel/android/OtelPlatformProviderTest.kt
deleted file mode 100644
index f47e3b65ab..0000000000
--- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/logging/otel/android/OtelPlatformProviderTest.kt
+++ /dev/null
@@ -1,903 +0,0 @@
-package com.onesignal.debug.internal.logging.otel.android
-
-import android.content.Context
-import android.content.SharedPreferences
-import android.os.Build
-import androidx.test.core.app.ApplicationProvider
-import br.com.colman.kotest.android.extensions.robolectric.RobolectricTest
-import com.onesignal.common.OneSignalUtils
-import com.onesignal.common.OneSignalWrapper
-import com.onesignal.core.internal.config.ConfigModel
-import com.onesignal.core.internal.preferences.PreferenceOneSignalKeys
-import com.onesignal.core.internal.preferences.PreferenceStores
-import com.onesignal.debug.LogLevel
-import com.onesignal.debug.internal.logging.Logging
-import com.onesignal.user.internal.backend.IdentityConstants
-import io.kotest.core.spec.style.FunSpec
-import io.kotest.matchers.shouldBe
-import io.kotest.matchers.shouldNotBe
-import io.kotest.matchers.string.shouldContain
-import io.mockk.every
-import io.mockk.mockk
-import io.mockk.slot
-import kotlinx.coroutines.runBlocking
-import org.json.JSONArray
-import org.json.JSONObject
-import org.robolectric.annotation.Config
-import com.onesignal.core.internal.config.CONFIG_NAME_SPACE as configNameSpace
-import com.onesignal.user.internal.identity.IDENTITY_NAME_SPACE as identityNameSpace
-
-@RobolectricTest
-@Config(sdk = [Build.VERSION_CODES.O])
-class OtelPlatformProviderTest : FunSpec({
-
- var appContext: Context? = null
- var sharedPreferences: SharedPreferences? = null
-
- beforeAny {
- if (appContext == null) {
- appContext = ApplicationProvider.getApplicationContext()
- sharedPreferences = appContext!!.getSharedPreferences(PreferenceStores.ONESIGNAL, Context.MODE_PRIVATE)
- }
- }
-
- beforeEach {
- // Ensure sharedPreferences is initialized
- if (sharedPreferences == null) {
- appContext = ApplicationProvider.getApplicationContext()
- sharedPreferences = appContext!!.getSharedPreferences(PreferenceStores.ONESIGNAL, Context.MODE_PRIVATE)
- }
- // Clear SharedPreferences and reset wrapper
- sharedPreferences!!.edit().clear().commit()
- OneSignalWrapper.sdkType = null
- OneSignalWrapper.sdkVersion = null
- Logging.logLevel = LogLevel.NONE
- }
-
- afterEach {
- // Clean up
- sharedPreferences!!.edit().clear().commit()
- OneSignalWrapper.sdkType = null
- OneSignalWrapper.sdkVersion = null
- }
-
- // ===== Static Properties Tests =====
-
- test("sdkBase returns android") {
- // Given
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- // When
- val result = provider.sdkBase
-
- // Then
- result shouldBe "android"
- }
-
- test("sdkBaseVersion returns OneSignalUtils.sdkVersion") {
- // Given
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- // When
- val result = provider.sdkBaseVersion
-
- // Then
- result shouldBe OneSignalUtils.sdkVersion
- }
-
- test("appPackageId returns context.packageName") {
- // Given
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- // When
- val result = provider.appPackageId
-
- // Then
- result shouldBe appContext!!.packageName
- }
-
- test("appVersion returns AndroidUtils.getAppVersion") {
- // Given
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- // When
- val result = provider.appVersion
-
- // Then
- result shouldNotBe null
- result shouldNotBe ""
- }
-
- test("deviceManufacturer returns Build.MANUFACTURER") {
- // Given
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- // When
- val result = provider.deviceManufacturer
-
- // Then
- result shouldBe Build.MANUFACTURER
- }
-
- test("deviceModel returns Build.MODEL") {
- // Given
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- // When
- val result = provider.deviceModel
-
- // Then
- result shouldBe Build.MODEL
- }
-
- test("osName returns Android") {
- // Given
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- // When
- val result = provider.osName
-
- // Then
- result shouldBe "Android"
- }
-
- test("osVersion returns Build.VERSION.RELEASE") {
- // Given
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- // When
- val result = provider.osVersion
-
- // Then
- result shouldBe Build.VERSION.RELEASE
- }
-
- test("osBuildId returns Build.ID") {
- // Given
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- // When
- val result = provider.osBuildId
-
- // Then
- result shouldBe Build.ID
- }
-
- test("sdkWrapper returns OneSignalWrapper.sdkType") {
- // Given
- OneSignalWrapper.sdkType = "Unity"
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- // When
- val result = provider.sdkWrapper
-
- // Then
- result shouldBe "Unity"
- }
-
- test("sdkWrapper returns null when not set") {
- // Given
- OneSignalWrapper.sdkType = null
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- // When
- val result = provider.sdkWrapper
-
- // Then
- result shouldBe null
- }
-
- test("sdkWrapperVersion returns OneSignalWrapper.sdkVersion") {
- // Given
- OneSignalWrapper.sdkVersion = "1.0.0"
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- // When
- val result = provider.sdkWrapperVersion
-
- // Then
- result shouldBe "1.0.0"
- }
-
- test("sdkWrapperVersion returns null when not set") {
- // Given
- OneSignalWrapper.sdkVersion = null
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- // When
- val result = provider.sdkWrapperVersion
-
- // Then
- result shouldBe null
- }
-
- // ===== Lazy ID Properties Tests =====
-
- test("appId returns resolved appId from OtelIdResolver") {
- // Given
- val configModel = JSONObject().apply {
- put(ConfigModel::appId.name, "test-app-id-123")
- }
- val configArray = JSONArray().apply {
- put(configModel)
- }
- sharedPreferences!!.edit()
- .putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, configArray.toString())
- .commit()
-
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- // When
- val result = provider.appId
-
- // Then
- result shouldBe "test-app-id-123"
- }
-
- test("appId returns error UUID when not available") {
- // Given
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- // When
- val result = provider.appId
-
- // Then - should return error appId (not null, but error UUID prefix)
- result shouldNotBe null
- result shouldContain "e1100000-0000-4000-a000-"
- }
-
- test("onesignalId returns resolved onesignalId from OtelIdResolver") {
- // Given
- val identityModel = JSONObject().apply {
- put(IdentityConstants.ONESIGNAL_ID, "test-onesignal-id-123")
- }
- val identityArray = JSONArray().apply {
- put(identityModel)
- }
- sharedPreferences!!.edit()
- .putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + identityNameSpace, identityArray.toString())
- .commit()
-
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- // When
- val result = provider.onesignalId
-
- // Then
- result shouldBe "test-onesignal-id-123"
- }
-
- test("onesignalId returns null when not available") {
- // Given
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- // When
- val result = provider.onesignalId
-
- // Then
- result shouldBe null
- }
-
- test("pushSubscriptionId returns resolved pushSubscriptionId from OtelIdResolver") {
- // Given
- val configModel = JSONObject().apply {
- put(ConfigModel::pushSubscriptionId.name, "test-push-sub-id-123")
- }
- val configArray = JSONArray().apply {
- put(configModel)
- }
- sharedPreferences!!.edit()
- .putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, configArray.toString())
- .commit()
-
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- // When
- val result = provider.pushSubscriptionId
-
- // Then
- result shouldBe "test-push-sub-id-123"
- }
-
- test("pushSubscriptionId returns null when not available") {
- // Given
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- // When
- val result = provider.pushSubscriptionId
-
- // Then
- result shouldBe null
- }
-
- // ===== appState Tests =====
-
- test("appState returns foreground when getIsInForeground returns true") {
- // Given
- val getIsInForeground: () -> Boolean? = { true }
- val config = OtelPlatformProviderConfig(
- crashStoragePath = "/test/path",
- appPackageId = "com.test",
- appVersion = "1.0",
- context = appContext,
- getIsInForeground = getIsInForeground
- )
- val provider = OtelPlatformProvider(config)
-
- // When
- val result = provider.appState
-
- // Then
- result shouldBe "foreground"
- }
-
- test("appState returns background when getIsInForeground returns false") {
- // Given
- val getIsInForeground: () -> Boolean? = { false }
- val config = OtelPlatformProviderConfig(
- crashStoragePath = "/test/path",
- appPackageId = "com.test",
- appVersion = "1.0",
- context = appContext,
- getIsInForeground = getIsInForeground
- )
- val provider = OtelPlatformProvider(config)
-
- // When
- val result = provider.appState
-
- // Then
- result shouldBe "background"
- }
-
- test("appState falls back to ActivityManager when getIsInForeground is null") {
- // Given
- val config = OtelPlatformProviderConfig(
- crashStoragePath = "/test/path",
- appPackageId = "com.test",
- appVersion = "1.0",
- context = appContext,
- getIsInForeground = null
- )
- val provider = OtelPlatformProvider(config)
-
- // When
- val result = provider.appState
-
- // Then - should return a valid state (foreground, background, or unknown)
- result shouldBeOneOf listOf("foreground", "background", "unknown")
- }
-
- test("appState returns unknown when context is null and getIsInForeground is null") {
- // Given
- val config = OtelPlatformProviderConfig(
- crashStoragePath = "/test/path",
- appPackageId = "com.test",
- appVersion = "1.0",
- context = null,
- getIsInForeground = null
- )
- val provider = OtelPlatformProvider(config)
-
- // When
- val result = provider.appState
-
- // Then
- result shouldBe "unknown"
- }
-
- test("appState handles exceptions gracefully and returns unknown") {
- // Given
- val mockContext = mockk(relaxed = true)
- every { mockContext.getSystemService(any()) } throws RuntimeException("Test exception")
- val config = OtelPlatformProviderConfig(
- crashStoragePath = "/test/path",
- appPackageId = "com.test",
- appVersion = "1.0",
- context = mockContext,
- getIsInForeground = null
- )
- val provider = OtelPlatformProvider(config)
-
- // When
- val result = provider.appState
-
- // Then
- result shouldBe "unknown"
- }
-
- // ===== processUptime Tests =====
-
- test("processUptime returns uptime in milliseconds") {
- // Given
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- // When
- val result = provider.processUptime
-
- // Then
- (result >= 0) shouldBe true
- (result < 1000000.0) shouldBe true // Reasonable upper bound
- }
-
- // ===== currentThreadName Tests =====
-
- test("currentThreadName returns current thread name") {
- // Given
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- // When
- val result = provider.currentThreadName
-
- // Then
- result shouldNotBe null
- result shouldNotBe ""
- }
-
- // ===== crashStoragePath Tests =====
-
- test("crashStoragePath returns configured path") {
- // Given
- val expectedPath = "/test/crash/path"
- val config = OtelPlatformProviderConfig(
- crashStoragePath = expectedPath,
- appPackageId = "com.test",
- appVersion = "1.0"
- )
- val provider = OtelPlatformProvider(config)
-
- // When
- val result = provider.crashStoragePath
-
- // Then
- result shouldBe expectedPath
- }
-
- test("crashStoragePath logs info message on first access") {
- // Given
- val logSlot = slot()
- val expectedPath = "/test/crash/path"
- val config = OtelPlatformProviderConfig(
- crashStoragePath = expectedPath,
- appPackageId = "com.test",
- appVersion = "1.0"
- )
- val provider = OtelPlatformProvider(config)
-
- // When
- val result = provider.crashStoragePath
-
- // Then
- result shouldBe expectedPath
- // Note: We can't easily verify Logging.info was called without mocking Logging,
- // but the behavior is tested by ensuring the path is returned correctly
- }
-
- test("createAndroidOtelPlatformProvider sets correct crashStoragePath") {
- // Given & When
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- // Then
- provider.crashStoragePath shouldContain "onesignal"
- provider.crashStoragePath shouldContain "otel"
- provider.crashStoragePath shouldContain "crashes"
- }
-
- // ===== minFileAgeForReadMillis Tests =====
-
- test("minFileAgeForReadMillis returns default value") {
- // Given
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- // When
- val result = provider.minFileAgeForReadMillis
-
- // Then
- result shouldBe 5_000L
- }
-
- // ===== isRemoteLoggingEnabled Tests =====
- // Derived from logLevel presence: empty logging_config → disabled, has log_level → enabled
-
- test("isRemoteLoggingEnabled returns false when no config exists") {
- val provider = createAndroidOtelPlatformProvider(appContext!!)
- provider.isRemoteLoggingEnabled shouldBe false
- }
-
- test("isRemoteLoggingEnabled returns true when config has logLevel ERROR") {
- val remoteLoggingParams = JSONObject().apply {
- put("logLevel", "ERROR")
- }
- val configModel = JSONObject().apply {
- put(ConfigModel::remoteLoggingParams.name, remoteLoggingParams)
- }
- val configArray = JSONArray().apply {
- put(configModel)
- }
- sharedPreferences!!.edit()
- .putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, configArray.toString())
- .commit()
-
- val provider = createAndroidOtelPlatformProvider(appContext!!)
- provider.isRemoteLoggingEnabled shouldBe true
- }
-
- test("isRemoteLoggingEnabled returns false when logging_config is empty (no logLevel)") {
- val remoteLoggingParams = JSONObject()
- val configModel = JSONObject().apply {
- put(ConfigModel::remoteLoggingParams.name, remoteLoggingParams)
- }
- val configArray = JSONArray().apply {
- put(configModel)
- }
- sharedPreferences!!.edit()
- .putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, configArray.toString())
- .commit()
-
- val provider = createAndroidOtelPlatformProvider(appContext!!)
- provider.isRemoteLoggingEnabled shouldBe false
- }
-
- test("isRemoteLoggingEnabled returns false when logLevel is NONE") {
- val remoteLoggingParams = JSONObject().apply {
- put("logLevel", "NONE")
- }
- val configModel = JSONObject().apply {
- put(ConfigModel::remoteLoggingParams.name, remoteLoggingParams)
- }
- val configArray = JSONArray().apply {
- put(configModel)
- }
- sharedPreferences!!.edit()
- .putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, configArray.toString())
- .commit()
-
- val provider = createAndroidOtelPlatformProvider(appContext!!)
- provider.isRemoteLoggingEnabled shouldBe false
- }
-
- test("isRemoteLoggingEnabled returns false when exception occurs") {
- val mockContext = mockk(relaxed = true)
- every { mockContext.getSharedPreferences(any(), any()) } throws RuntimeException("Test exception")
- val config = OtelPlatformProviderConfig(
- crashStoragePath = "/test/path",
- appPackageId = "com.test",
- appVersion = "1.0",
- context = mockContext
- )
- val provider = OtelPlatformProvider(config)
- provider.isRemoteLoggingEnabled shouldBe false
- }
-
- // ===== remoteLogLevel Tests =====
-
- test("remoteLogLevel returns null when no config exists (disabled)") {
- // Given
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- // When
- val result = provider.remoteLogLevel
-
- // Then
- result shouldBe null
- }
-
- test("remoteLogLevel returns null when logging_config is empty (disabled)") {
- // Given
- val remoteLoggingParams = JSONObject()
- val configModel = JSONObject().apply {
- put(ConfigModel::remoteLoggingParams.name, remoteLoggingParams)
- }
- val configArray = JSONArray().apply {
- put(configModel)
- }
- sharedPreferences!!.edit()
- .putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, configArray.toString())
- .commit()
-
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- // When
- val result = provider.remoteLogLevel
-
- // Then
- result shouldBe null
- }
-
- test("remoteLogLevel returns configLevel name when available") {
- // Given
- val remoteLoggingParams = JSONObject().apply {
- put("logLevel", "WARN")
- }
- val configModel = JSONObject().apply {
- put(ConfigModel::remoteLoggingParams.name, remoteLoggingParams)
- }
- val configArray = JSONArray().apply {
- put(configModel)
- }
- sharedPreferences!!.edit()
- .putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, configArray.toString())
- .commit()
-
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- // When
- val result = provider.remoteLogLevel
-
- // Then
- result shouldBe "WARN"
- }
-
- test("remoteLogLevel returns ERROR when configLevel is ERROR") {
- // Given
- val remoteLoggingParams = JSONObject().apply {
- put("logLevel", "ERROR")
- }
- val configModel = JSONObject().apply {
- put(ConfigModel::remoteLoggingParams.name, remoteLoggingParams)
- }
- val configArray = JSONArray().apply {
- put(configModel)
- }
- sharedPreferences!!.edit()
- .putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, configArray.toString())
- .commit()
-
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- // When
- val result = provider.remoteLogLevel
-
- // Then
- result shouldBe "ERROR"
- }
-
- test("remoteLogLevel returns NONE when configLevel is NONE") {
- // Given
- val remoteLoggingParams = JSONObject().apply {
- put("logLevel", "NONE")
- }
- val configModel = JSONObject().apply {
- put(ConfigModel::remoteLoggingParams.name, remoteLoggingParams)
- }
- val configArray = JSONArray().apply {
- put(configModel)
- }
- sharedPreferences!!.edit()
- .putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, configArray.toString())
- .commit()
-
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- // When
- val result = provider.remoteLogLevel
-
- // Then
- result shouldBe "NONE"
- }
-
- test("remoteLogLevel returns null when exception occurs") {
- // Given
- val mockContext = mockk(relaxed = true)
- every { mockContext.getSharedPreferences(any(), any()) } throws RuntimeException("Test exception")
- val config = OtelPlatformProviderConfig(
- crashStoragePath = "/test/path",
- appPackageId = "com.test",
- appVersion = "1.0",
- context = mockContext
- )
- val provider = OtelPlatformProvider(config)
-
- // When
- val result = provider.remoteLogLevel
-
- // Then
- result shouldBe null
- }
-
- // ===== appIdForHeaders Tests =====
-
- test("appIdForHeaders returns appId when available") {
- // Given
- val configModel = JSONObject().apply {
- put(ConfigModel::appId.name, "test-app-id-123")
- }
- val configArray = JSONArray().apply {
- put(configModel)
- }
- sharedPreferences!!.edit()
- .putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, configArray.toString())
- .commit()
-
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- // When
- val result = provider.appIdForHeaders
-
- // Then
- result shouldBe "test-app-id-123"
- }
-
- test("appIdForHeaders returns empty string when appId is null") {
- // Given
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- // When
- val result = provider.appIdForHeaders
-
- // Then - even with error appId, it should return something (not empty)
- result shouldNotBe null
- }
-
- // ===== apiBaseUrl Tests =====
-
- test("apiBaseUrl returns the core module base URL") {
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- provider.apiBaseUrl shouldBe com.onesignal.core.internal.http.OneSignalService.ONESIGNAL_API_BASE_URL
- }
-
- // ===== getInstallId Tests =====
-
- test("getInstallId returns installId from SharedPreferences") {
- // Given
- sharedPreferences!!.edit()
- .putString(PreferenceOneSignalKeys.PREFS_OS_INSTALL_ID, "test-install-id-123")
- .commit()
-
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- // When
- val result = runBlocking { provider.getInstallId() }
-
- // Then
- result shouldBe "test-install-id-123"
- }
-
- test("getInstallId returns default when not found") {
- // Given
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- // When
- val result = runBlocking { provider.getInstallId() }
-
- // Then
- result shouldBe "InstallId-Null"
- }
-
- // ===== Factory Function Tests =====
-
- test("createAndroidOtelPlatformProvider creates provider with correct config") {
- // Given & When
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- // Then
- provider.appPackageId shouldBe appContext!!.packageName
- provider.sdkBase shouldBe "android"
- provider.osName shouldBe "Android"
- }
-
- // ===== Fresh install / all-missing scenario =====
-
- test("fresh install: all lazy properties return safe defaults without crashing") {
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- provider.appId shouldContain "e1100000-0000-4000-a000-"
- provider.onesignalId shouldBe null
- provider.pushSubscriptionId shouldBe null
- provider.isRemoteLoggingEnabled shouldBe false
- provider.remoteLogLevel shouldBe null
- provider.appIdForHeaders shouldNotBe null
- provider.sdkBase shouldBe "android"
- provider.osName shouldBe "Android"
- provider.crashStoragePath shouldContain "onesignal"
- }
-
- test("lazy properties cache the initial value and ignore later SharedPreferences changes") {
- val provider = createAndroidOtelPlatformProvider(appContext!!)
-
- provider.isRemoteLoggingEnabled shouldBe false
- provider.remoteLogLevel shouldBe null
-
- val remoteLoggingParams = JSONObject().apply { put("logLevel", "ERROR") }
- val configModel = JSONObject().apply {
- put(ConfigModel::remoteLoggingParams.name, remoteLoggingParams)
- }
- val configArray = JSONArray().apply { put(configModel) }
- sharedPreferences!!.edit()
- .putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, configArray.toString())
- .commit()
-
- provider.isRemoteLoggingEnabled shouldBe false
- provider.remoteLogLevel shouldBe null
- }
-
- test("getIsInForeground callback throws — appState returns unknown") {
- val config = OtelPlatformProviderConfig(
- crashStoragePath = "/test/path",
- appPackageId = "com.test",
- appVersion = "1.0",
- context = appContext,
- getIsInForeground = { throw RuntimeException("callback boom") }
- )
- val provider = OtelPlatformProvider(config)
- provider.appState shouldBe "unknown"
- }
-
- test("getIsInForeground returns null — falls back to ActivityManager") {
- val config = OtelPlatformProviderConfig(
- crashStoragePath = "/test/path",
- appPackageId = "com.test",
- appVersion = "1.0",
- context = appContext,
- getIsInForeground = { null }
- )
- val provider = OtelPlatformProvider(config)
- provider.appState shouldBeOneOf listOf("foreground", "background", "unknown")
- }
-
- test("null context and null callback — all provider properties return safe defaults") {
- val config = OtelPlatformProviderConfig(
- crashStoragePath = "/test/path",
- appPackageId = "com.test",
- appVersion = "1.0",
- context = null,
- getIsInForeground = null
- )
- val provider = OtelPlatformProvider(config)
-
- provider.appState shouldBe "unknown"
- provider.appPackageId shouldBe "com.test"
- provider.appVersion shouldBe "1.0"
- provider.crashStoragePath shouldBe "/test/path"
- provider.isRemoteLoggingEnabled shouldBe false
- provider.remoteLogLevel shouldBe null
- }
-
- test("corrupted SharedPreferences JSON — isRemoteLoggingEnabled returns false") {
- sharedPreferences!!.edit()
- .putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, "not valid json {{{")
- .commit()
-
- val provider = createAndroidOtelPlatformProvider(appContext!!)
- provider.isRemoteLoggingEnabled shouldBe false
- provider.remoteLogLevel shouldBe null
- }
-
- test("corrupted SharedPreferences JSON — appId returns error UUID") {
- sharedPreferences!!.edit()
- .putString(PreferenceOneSignalKeys.MODEL_STORE_PREFIX + configNameSpace, "not valid json {{{")
- .commit()
-
- val provider = createAndroidOtelPlatformProvider(appContext!!)
- provider.appId shouldContain "e1100000-0000-4000-a000-"
- }
-
- // ===== Factory Function Tests =====
-
- test("createAndroidOtelPlatformProvider handles null appVersion gracefully") {
- // Given
- val mockContext = mockk(relaxed = true)
- val mockPackageManager = mockk(relaxed = true)
- every { mockContext.packageName } returns "com.test"
- every { mockContext.cacheDir } returns appContext!!.cacheDir
- every { mockContext.packageManager } returns mockPackageManager
- every { mockContext.getSharedPreferences(any(), any()) } returns sharedPreferences
- // Make getPackageInfo throw NameNotFoundException to simulate missing package
- every { mockPackageManager.getPackageInfo(any(), any()) } throws android.content.pm.PackageManager.NameNotFoundException()
-
- // When
- val provider: OtelPlatformProvider = createAndroidOtelPlatformProvider(mockContext)
-
- // Then
- provider.appVersion shouldBe "unknown"
- }
-})
-
-// Helper extension for shouldBeOneOf
-private infix fun T.shouldBeOneOf(expected: List) {
- val isInList = expected.contains(this)
- if (!isInList) {
- throw AssertionError("Expected $this to be one of $expected")
- }
-}
diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/internal/OtelConfigEvaluatorTest.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/internal/OtelConfigEvaluatorTest.kt
deleted file mode 100644
index 6fd5478cdd..0000000000
--- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/internal/OtelConfigEvaluatorTest.kt
+++ /dev/null
@@ -1,102 +0,0 @@
-package com.onesignal.internal
-
-import com.onesignal.debug.LogLevel
-import io.kotest.core.spec.style.FunSpec
-import io.kotest.matchers.shouldBe
-import io.kotest.matchers.types.shouldBeInstanceOf
-
-class OtelConfigEvaluatorTest : FunSpec({
-
- // ---- null -> enabled ----
-
- test("null old config and new enabled returns Enable with the configured level") {
- val result = OtelConfigEvaluator.evaluate(
- old = null,
- new = OtelConfig(isEnabled = true, logLevel = LogLevel.WARN),
- )
- result.shouldBeInstanceOf()
- result.logLevel shouldBe LogLevel.WARN
- }
-
- test("null old config and new enabled with null logLevel defaults to ERROR") {
- val result = OtelConfigEvaluator.evaluate(
- old = null,
- new = OtelConfig(isEnabled = true, logLevel = null),
- )
- result.shouldBeInstanceOf()
- result.logLevel shouldBe LogLevel.ERROR
- }
-
- // ---- null -> disabled ----
-
- test("null old config and new disabled returns NoChange") {
- val result = OtelConfigEvaluator.evaluate(
- old = null,
- new = OtelConfig(isEnabled = false, logLevel = null),
- )
- result shouldBe OtelConfigAction.NoChange
- }
-
- // ---- disabled -> enabled ----
-
- test("disabled to enabled returns Enable") {
- val result = OtelConfigEvaluator.evaluate(
- old = OtelConfig.DISABLED,
- new = OtelConfig(isEnabled = true, logLevel = LogLevel.INFO),
- )
- result.shouldBeInstanceOf()
- result.logLevel shouldBe LogLevel.INFO
- }
-
- // ---- enabled -> disabled ----
-
- test("enabled to disabled returns Disable") {
- val result = OtelConfigEvaluator.evaluate(
- old = OtelConfig(isEnabled = true, logLevel = LogLevel.ERROR),
- new = OtelConfig(isEnabled = false, logLevel = null),
- )
- result shouldBe OtelConfigAction.Disable
- }
-
- // ---- enabled -> enabled (level changed) ----
-
- test("enabled to enabled with different log level returns UpdateLogLevel") {
- val result = OtelConfigEvaluator.evaluate(
- old = OtelConfig(isEnabled = true, logLevel = LogLevel.ERROR),
- new = OtelConfig(isEnabled = true, logLevel = LogLevel.WARN),
- )
- result.shouldBeInstanceOf()
- result.oldLevel shouldBe LogLevel.ERROR
- result.newLevel shouldBe LogLevel.WARN
- }
-
- test("enabled with null level to enabled with explicit level returns UpdateLogLevel") {
- val result = OtelConfigEvaluator.evaluate(
- old = OtelConfig(isEnabled = true, logLevel = null),
- new = OtelConfig(isEnabled = true, logLevel = LogLevel.WARN),
- )
- result.shouldBeInstanceOf()
- result.oldLevel shouldBe LogLevel.ERROR
- result.newLevel shouldBe LogLevel.WARN
- }
-
- // ---- enabled -> enabled (same level) ----
-
- test("enabled to enabled with same level returns NoChange") {
- val result = OtelConfigEvaluator.evaluate(
- old = OtelConfig(isEnabled = true, logLevel = LogLevel.ERROR),
- new = OtelConfig(isEnabled = true, logLevel = LogLevel.ERROR),
- )
- result shouldBe OtelConfigAction.NoChange
- }
-
- // ---- disabled -> disabled ----
-
- test("disabled to disabled returns NoChange") {
- val result = OtelConfigEvaluator.evaluate(
- old = OtelConfig.DISABLED,
- new = OtelConfig.DISABLED,
- )
- result shouldBe OtelConfigAction.NoChange
- }
-})
diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/internal/OtelLifecycleManagerFaultTest.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/internal/OtelLifecycleManagerFaultTest.kt
deleted file mode 100644
index 7cd5fb6418..0000000000
--- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/internal/OtelLifecycleManagerFaultTest.kt
+++ /dev/null
@@ -1,311 +0,0 @@
-package com.onesignal.internal
-
-import android.content.Context
-import android.os.Build
-import androidx.test.core.app.ApplicationProvider
-import br.com.colman.kotest.android.extensions.robolectric.RobolectricTest
-import com.onesignal.common.modeling.ModelChangeTags
-import com.onesignal.core.internal.config.ConfigModel
-import com.onesignal.debug.LogLevel
-import com.onesignal.debug.internal.crash.OtelSdkSupport
-import com.onesignal.debug.internal.logging.Logging
-import com.onesignal.debug.internal.logging.otel.android.OtelPlatformProvider
-import com.onesignal.debug.internal.logging.otel.android.OtelPlatformProviderConfig
-import com.onesignal.otel.IOtelCrashHandler
-import com.onesignal.otel.IOtelLogger
-import com.onesignal.otel.IOtelOpenTelemetryRemote
-import com.onesignal.otel.IOtelPlatformProvider
-import com.onesignal.otel.crash.IOtelAnrDetector
-import io.kotest.core.spec.style.FunSpec
-import io.kotest.matchers.shouldBe
-import io.mockk.every
-import io.mockk.mockk
-import io.mockk.verify
-import org.robolectric.annotation.Config
-
-/**
- * Fault injection tests that prove all try/catch(Throwable) wrappers in
- * [OtelLifecycleManager] actually catch and suppress exceptions, and that
- * a failure in one feature does not prevent others from starting.
- */
-@RobolectricTest
-@Config(sdk = [Build.VERSION_CODES.O])
-class OtelLifecycleManagerFaultTest : FunSpec({
-
- lateinit var context: Context
- lateinit var mockCrashHandler: IOtelCrashHandler
- lateinit var mockAnrDetector: IOtelAnrDetector
- lateinit var mockTelemetry: IOtelOpenTelemetryRemote
- lateinit var mockLogger: IOtelLogger
- lateinit var mockPlatformProvider: OtelPlatformProvider
-
- beforeEach {
- context = ApplicationProvider.getApplicationContext()
- OtelSdkSupport.isSupported = true
-
- mockCrashHandler = mockk(relaxed = true)
- mockAnrDetector = mockk(relaxed = true)
- mockTelemetry = mockk(relaxed = true)
- mockLogger = mockk(relaxed = true)
- mockPlatformProvider = OtelPlatformProvider(
- OtelPlatformProviderConfig(
- crashStoragePath = "/test/path",
- appPackageId = "com.test",
- appVersion = "1.0",
- context = context,
- )
- )
- }
-
- afterEach {
- OtelSdkSupport.reset()
- Logging.setOtelTelemetry(null) { false }
- }
-
- fun createManager(
- crashFactory: (Context, IOtelLogger) -> IOtelCrashHandler = { _, _ -> mockCrashHandler },
- anrFactory: (IOtelPlatformProvider, IOtelLogger, Long, Long) -> IOtelAnrDetector = { _, _, _, _ -> mockAnrDetector },
- telemetryFactory: (IOtelPlatformProvider) -> IOtelOpenTelemetryRemote = { mockTelemetry },
- ppFactory: (Context) -> OtelPlatformProvider = { mockPlatformProvider },
- ): OtelLifecycleManager =
- OtelLifecycleManager(
- context = context,
- crashHandlerFactory = crashFactory,
- anrDetectorFactory = anrFactory,
- remoteTelemetryFactory = telemetryFactory,
- platformProviderFactory = ppFactory,
- loggerFactory = { mockLogger },
- )
-
- // ------------------------------------------------------------------
- // Factory-level fault injection: factory itself throws
- // ------------------------------------------------------------------
-
- test("crash handler factory throws — ANR and logging still start") {
- var telemetryCreated = false
- val manager = createManager(
- crashFactory = { _, _ -> throw RuntimeException("crash factory boom") },
- telemetryFactory = { telemetryCreated = true; mockTelemetry },
- )
- manager.onModelReplaced(configWith(isEnabled = true, logLevel = LogLevel.ERROR), ModelChangeTags.HYDRATE)
-
- verify(exactly = 1) { mockAnrDetector.start() }
- telemetryCreated shouldBe true
- }
-
- test("ANR factory throws — crash handler and logging still start") {
- var telemetryCreated = false
- val manager = createManager(
- anrFactory = { _, _, _, _ -> throw RuntimeException("anr factory boom") },
- telemetryFactory = { telemetryCreated = true; mockTelemetry },
- )
- manager.onModelReplaced(configWith(isEnabled = true, logLevel = LogLevel.ERROR), ModelChangeTags.HYDRATE)
-
- verify(exactly = 1) { mockCrashHandler.initialize() }
- telemetryCreated shouldBe true
- }
-
- test("telemetry factory throws — crash handler and ANR still start") {
- val manager = createManager(
- telemetryFactory = { throw RuntimeException("telemetry factory boom") },
- )
- manager.onModelReplaced(configWith(isEnabled = true, logLevel = LogLevel.ERROR), ModelChangeTags.HYDRATE)
-
- verify(exactly = 1) { mockCrashHandler.initialize() }
- verify(exactly = 1) { mockAnrDetector.start() }
- }
-
- test("all three factories throw — no exception propagates") {
- val manager = createManager(
- crashFactory = { _, _ -> throw RuntimeException("crash") },
- anrFactory = { _, _, _, _ -> throw RuntimeException("anr") },
- telemetryFactory = { throw RuntimeException("telemetry") },
- )
- manager.onModelReplaced(configWith(isEnabled = true, logLevel = LogLevel.ERROR), ModelChangeTags.HYDRATE)
- }
-
- // ------------------------------------------------------------------
- // Initialize-level fault injection: object created but init throws
- // ------------------------------------------------------------------
-
- test("crash handler initialize() throws — ANR and logging still start") {
- every { mockCrashHandler.initialize() } throws RuntimeException("init boom")
- var telemetryCreated = false
-
- val manager = createManager(
- telemetryFactory = { telemetryCreated = true; mockTelemetry },
- )
- manager.onModelReplaced(configWith(isEnabled = true, logLevel = LogLevel.ERROR), ModelChangeTags.HYDRATE)
-
- verify(exactly = 1) { mockAnrDetector.start() }
- telemetryCreated shouldBe true
- }
-
- test("ANR detector start() throws — crash handler and logging still start") {
- every { mockAnrDetector.start() } throws RuntimeException("start boom")
- var telemetryCreated = false
-
- val manager = createManager(
- telemetryFactory = { telemetryCreated = true; mockTelemetry },
- )
- manager.onModelReplaced(configWith(isEnabled = true, logLevel = LogLevel.ERROR), ModelChangeTags.HYDRATE)
-
- verify(exactly = 1) { mockCrashHandler.initialize() }
- telemetryCreated shouldBe true
- }
-
- // ------------------------------------------------------------------
- // Disable-level fault injection: shutdown/stop/unregister throws
- // ------------------------------------------------------------------
-
- test("ANR stop() throws during disable — crash unregister and telemetry shutdown still run") {
- every { mockAnrDetector.stop() } throws RuntimeException("stop boom")
-
- val manager = createManager()
- manager.onModelReplaced(configWith(isEnabled = true, logLevel = LogLevel.ERROR), ModelChangeTags.HYDRATE)
- manager.onModelReplaced(configWith(isEnabled = false, logLevel = null), ModelChangeTags.HYDRATE)
-
- verify(exactly = 1) { mockCrashHandler.unregister() }
- verify(exactly = 1) { mockTelemetry.shutdown() }
- }
-
- test("crash handler unregister() throws during disable — telemetry shutdown still runs") {
- every { mockCrashHandler.unregister() } throws RuntimeException("unregister boom")
-
- val manager = createManager()
- manager.onModelReplaced(configWith(isEnabled = true, logLevel = LogLevel.ERROR), ModelChangeTags.HYDRATE)
- manager.onModelReplaced(configWith(isEnabled = false, logLevel = null), ModelChangeTags.HYDRATE)
-
- verify(exactly = 1) { mockTelemetry.shutdown() }
- }
-
- test("telemetry shutdown() throws during disable — no exception propagates") {
- every { mockTelemetry.shutdown() } throws RuntimeException("shutdown boom")
-
- val manager = createManager()
- manager.onModelReplaced(configWith(isEnabled = true, logLevel = LogLevel.ERROR), ModelChangeTags.HYDRATE)
- manager.onModelReplaced(configWith(isEnabled = false, logLevel = null), ModelChangeTags.HYDRATE)
-
- verify(exactly = 1) { mockAnrDetector.stop() }
- verify(exactly = 1) { mockCrashHandler.unregister() }
- }
-
- // ------------------------------------------------------------------
- // Platform provider fault injection
- // ------------------------------------------------------------------
-
- test("platform provider factory throws — initializeFromCachedConfig does not propagate") {
- val manager = createManager(
- ppFactory = { throw RuntimeException("provider boom") },
- )
- manager.initializeFromCachedConfig()
- }
-
- // ------------------------------------------------------------------
- // UpdateLogLevel fault injection
- // ------------------------------------------------------------------
-
- test("telemetry factory throws during log level update — no exception propagates") {
- var callCount = 0
- val manager = createManager(
- telemetryFactory = {
- callCount++
- if (callCount > 1) throw RuntimeException("second create boom")
- mockTelemetry
- },
- )
- manager.onModelReplaced(configWith(isEnabled = true, logLevel = LogLevel.ERROR), ModelChangeTags.HYDRATE)
- manager.onModelReplaced(configWith(isEnabled = true, logLevel = LogLevel.WARN), ModelChangeTags.HYDRATE)
- }
-
- // ------------------------------------------------------------------
- // Idempotency: calling enable twice doesn't double-create
- // ------------------------------------------------------------------
-
- test("enable called twice does not create duplicate crash handler or ANR detector") {
- val manager = createManager()
- manager.onModelReplaced(configWith(isEnabled = true, logLevel = LogLevel.ERROR), ModelChangeTags.HYDRATE)
- manager.onModelReplaced(configWith(isEnabled = false, logLevel = null), ModelChangeTags.HYDRATE)
- manager.onModelReplaced(configWith(isEnabled = true, logLevel = LogLevel.WARN), ModelChangeTags.HYDRATE)
-
- verify(exactly = 2) { mockCrashHandler.initialize() }
- verify(exactly = 2) { mockAnrDetector.start() }
- }
-
- // ------------------------------------------------------------------
- // Verify mock interactions in happy path
- // ------------------------------------------------------------------
-
- test("enable creates all three features and disable tears all down") {
- val manager = createManager()
-
- manager.onModelReplaced(configWith(isEnabled = true, logLevel = LogLevel.ERROR), ModelChangeTags.HYDRATE)
- verify(exactly = 1) { mockCrashHandler.initialize() }
- verify(exactly = 1) { mockAnrDetector.start() }
-
- manager.onModelReplaced(configWith(isEnabled = false, logLevel = null), ModelChangeTags.HYDRATE)
- verify(exactly = 1) { mockCrashHandler.unregister() }
- verify(exactly = 1) { mockAnrDetector.stop() }
- verify { mockTelemetry.shutdown() }
- }
-
- test("update log level shuts down old telemetry and creates new one") {
- var createCount = 0
- val telemetry1 = mockk(relaxed = true)
- val telemetry2 = mockk(relaxed = true)
- val manager = createManager(
- telemetryFactory = {
- createCount++
- if (createCount == 1) telemetry1 else telemetry2
- },
- )
-
- manager.onModelReplaced(configWith(isEnabled = true, logLevel = LogLevel.ERROR), ModelChangeTags.HYDRATE)
- manager.onModelReplaced(configWith(isEnabled = true, logLevel = LogLevel.WARN), ModelChangeTags.HYDRATE)
-
- verify(exactly = 1) { telemetry1.shutdown() }
- createCount shouldBe 2
- }
-
- // ------------------------------------------------------------------
- // Error type coverage: OutOfMemoryError, StackOverflowError
- // ------------------------------------------------------------------
-
- test("OutOfMemoryError from factory does not propagate") {
- val manager = createManager(
- crashFactory = { _, _ -> throw OutOfMemoryError("oom") },
- )
- manager.onModelReplaced(configWith(isEnabled = true, logLevel = LogLevel.ERROR), ModelChangeTags.HYDRATE)
-
- verify(exactly = 1) { mockAnrDetector.start() }
- }
-
- test("StackOverflowError from factory does not propagate") {
- val manager = createManager(
- anrFactory = { _, _, _, _ -> throw StackOverflowError("stack overflow") },
- )
- manager.onModelReplaced(configWith(isEnabled = true, logLevel = LogLevel.ERROR), ModelChangeTags.HYDRATE)
-
- verify(exactly = 1) { mockCrashHandler.initialize() }
- }
-
- // ------------------------------------------------------------------
- // initializeFromCachedConfig fault injection
- // ------------------------------------------------------------------
-
- test("initializeFromCachedConfig catches factory failure and does not propagate") {
- val manager = createManager(
- crashFactory = { _, _ -> throw RuntimeException("crash") },
- anrFactory = { _, _, _, _ -> throw RuntimeException("anr") },
- telemetryFactory = { throw RuntimeException("telemetry") },
- )
- manager.initializeFromCachedConfig()
- }
-})
-
-private fun configWith(isEnabled: Boolean, logLevel: LogLevel?): ConfigModel {
- val config = ConfigModel()
- config.remoteLoggingParams.isEnabled = isEnabled
- logLevel?.let { config.remoteLoggingParams.logLevel = it }
- return config
-}
diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/internal/OtelLifecycleManagerTest.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/internal/OtelLifecycleManagerTest.kt
deleted file mode 100644
index c5c2380346..0000000000
--- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/internal/OtelLifecycleManagerTest.kt
+++ /dev/null
@@ -1,103 +0,0 @@
-package com.onesignal.internal
-
-import android.content.Context
-import android.os.Build
-import androidx.test.core.app.ApplicationProvider
-import br.com.colman.kotest.android.extensions.robolectric.RobolectricTest
-import com.onesignal.common.modeling.ModelChangeTags
-import com.onesignal.core.internal.config.ConfigModel
-import com.onesignal.debug.LogLevel
-import com.onesignal.debug.internal.crash.OtelSdkSupport
-import com.onesignal.debug.internal.logging.Logging
-import io.kotest.core.spec.style.FunSpec
-import org.robolectric.annotation.Config
-
-@RobolectricTest
-@Config(sdk = [Build.VERSION_CODES.O])
-class OtelLifecycleManagerTest : FunSpec({
- lateinit var context: Context
-
- beforeEach {
- context = ApplicationProvider.getApplicationContext()
- OtelSdkSupport.isSupported = true
- }
-
- afterEach {
- OtelSdkSupport.reset()
- }
-
- test("initializeFromCachedConfig does not crash when SDK unsupported") {
- OtelSdkSupport.isSupported = false
- val manager = OtelLifecycleManager(context)
- manager.initializeFromCachedConfig()
- }
-
- test("initializeFromCachedConfig does not throw on supported SDK") {
- val manager = OtelLifecycleManager(context)
- manager.initializeFromCachedConfig()
- }
-
- test("onModelReplaced does not crash when SDK unsupported") {
- OtelSdkSupport.isSupported = false
- val manager = OtelLifecycleManager(context)
- manager.onModelReplaced(configWith(isEnabled = true, logLevel = LogLevel.ERROR), ModelChangeTags.HYDRATE)
- }
-
- test("onModelReplaced ignores non-HYDRATE tags") {
- val manager = OtelLifecycleManager(context)
- manager.initializeFromCachedConfig()
- manager.onModelReplaced(configWith(isEnabled = true, logLevel = LogLevel.ERROR), ModelChangeTags.NORMAL)
- }
-
- test("onModelReplaced enable then disable does not throw") {
- val manager = OtelLifecycleManager(context)
- manager.initializeFromCachedConfig()
-
- manager.onModelReplaced(configWith(isEnabled = true, logLevel = LogLevel.ERROR), ModelChangeTags.HYDRATE)
- manager.onModelReplaced(configWith(isEnabled = false, logLevel = null), ModelChangeTags.HYDRATE)
- }
-
- test("onModelReplaced updates log level without throwing") {
- val manager = OtelLifecycleManager(context)
- manager.initializeFromCachedConfig()
-
- manager.onModelReplaced(configWith(isEnabled = true, logLevel = LogLevel.ERROR), ModelChangeTags.HYDRATE)
- manager.onModelReplaced(configWith(isEnabled = true, logLevel = LogLevel.WARN), ModelChangeTags.HYDRATE)
- }
-
- test("onModelReplaced with same config is no-op") {
- val manager = OtelLifecycleManager(context)
- manager.initializeFromCachedConfig()
-
- manager.onModelReplaced(configWith(isEnabled = true, logLevel = LogLevel.ERROR), ModelChangeTags.HYDRATE)
- manager.onModelReplaced(configWith(isEnabled = true, logLevel = LogLevel.ERROR), ModelChangeTags.HYDRATE)
- }
-
- test("disable clears Otel telemetry from Logging") {
- val manager = OtelLifecycleManager(context)
- manager.initializeFromCachedConfig()
-
- manager.onModelReplaced(configWith(isEnabled = true, logLevel = LogLevel.ERROR), ModelChangeTags.HYDRATE)
- manager.onModelReplaced(configWith(isEnabled = false, logLevel = null), ModelChangeTags.HYDRATE)
-
- Logging.info("test message after otel disabled")
- }
-
- test("full lifecycle: init -> enable -> update level -> disable -> re-enable") {
- val manager = OtelLifecycleManager(context)
- manager.initializeFromCachedConfig()
-
- manager.onModelReplaced(configWith(isEnabled = true, logLevel = LogLevel.ERROR), ModelChangeTags.HYDRATE)
- manager.onModelReplaced(configWith(isEnabled = true, logLevel = LogLevel.WARN), ModelChangeTags.HYDRATE)
- manager.onModelReplaced(configWith(isEnabled = true, logLevel = LogLevel.INFO), ModelChangeTags.HYDRATE)
- manager.onModelReplaced(configWith(isEnabled = false, logLevel = null), ModelChangeTags.HYDRATE)
- manager.onModelReplaced(configWith(isEnabled = true, logLevel = LogLevel.DEBUG), ModelChangeTags.HYDRATE)
- }
-})
-
-private fun configWith(isEnabled: Boolean, logLevel: LogLevel?): ConfigModel {
- val config = ConfigModel()
- config.remoteLoggingParams.isEnabled = isEnabled
- logLevel?.let { config.remoteLoggingParams.logLevel = it }
- return config
-}
diff --git a/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/InAppMessagesManager.kt b/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/InAppMessagesManager.kt
index c3b7e72d80..64ae3a9b82 100644
--- a/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/InAppMessagesManager.kt
+++ b/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/InAppMessagesManager.kt
@@ -446,7 +446,7 @@ internal class InAppMessagesManager(
Logging.debug("InAppMessagesManager.attemptToShowInAppMessage: $messageDisplayQueue")
// If there are IAMs in the queue and nothing showing, show first in the queue
if (paused) {
- Logging.debug(
+ Logging.warn(
"InAppMessagesManager.attemptToShowInAppMessage: In app messaging is currently paused, in app messages will not be shown!",
)
} else if (messageDisplayQueue.isEmpty()) {
diff --git a/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/backend/impl/InAppBackendService.kt b/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/backend/impl/InAppBackendService.kt
index 9bbd738d55..79d9a76099 100644
--- a/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/backend/impl/InAppBackendService.kt
+++ b/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/backend/impl/InAppBackendService.kt
@@ -202,7 +202,7 @@ internal class InAppBackendService(
statusCode: Int,
response: String?,
) {
- Logging.info("Encountered a $statusCode error while attempting in-app message $requestType request: $response")
+ Logging.error("Encountered a $statusCode error while attempting in-app message $requestType request: $response")
}
private suspend fun attemptFetchWithRetries(
diff --git a/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/display/impl/InAppDisplayer.kt b/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/display/impl/InAppDisplayer.kt
index 7bf7c14fb2..9c7115cad3 100644
--- a/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/display/impl/InAppDisplayer.kt
+++ b/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/display/impl/InAppDisplayer.kt
@@ -149,7 +149,7 @@ internal class InAppDisplayer(
} catch (e: Exception) {
// Need to check error message to only catch MissingWebViewPackageException as it isn't public
if (e.message != null && e.message!!.contains("No WebView installed")) {
- Logging.info("Error setting up WebView: ", e)
+ Logging.error("Error setting up WebView: ", e)
} else {
throw e
}
diff --git a/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/display/impl/InAppMessageView.kt b/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/display/impl/InAppMessageView.kt
index f4b8f1263a..a86a8bed6b 100644
--- a/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/display/impl/InAppMessageView.kt
+++ b/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/display/impl/InAppMessageView.kt
@@ -460,7 +460,7 @@ internal class InAppMessageView(
*/
suspend fun dismissAndAwaitNextMessage() {
if (draggableRelativeLayout == null) {
- Logging.info("No host presenter to trigger dismiss animation, counting as dismissed already")
+ Logging.error("No host presenter to trigger dismiss animation, counting as dismissed already")
dereferenceViews()
return
}
diff --git a/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/hydrators/InAppHydrator.kt b/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/hydrators/InAppHydrator.kt
index a860357bb6..0518dc5c72 100644
--- a/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/hydrators/InAppHydrator.kt
+++ b/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/hydrators/InAppHydrator.kt
@@ -30,7 +30,7 @@ internal class InAppHydrator(
try {
val content = InAppMessageContent(jsonObject)
if (content.contentHtml == null) {
- Logging.info("displayMessage:OnSuccess: No HTML retrieved from loadMessageContent")
+ Logging.debug("displayMessage:OnSuccess: No HTML retrieved from loadMessageContent")
return null
}
diff --git a/OneSignalSDK/onesignal/location/src/main/java/com/onesignal/location/internal/LocationManager.kt b/OneSignalSDK/onesignal/location/src/main/java/com/onesignal/location/internal/LocationManager.kt
index bd26095c5a..903183d369 100644
--- a/OneSignalSDK/onesignal/location/src/main/java/com/onesignal/location/internal/LocationManager.kt
+++ b/OneSignalSDK/onesignal/location/src/main/java/com/onesignal/location/internal/LocationManager.kt
@@ -102,7 +102,7 @@ internal class LocationManager(
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
if (!hasFinePermissionGranted && !hasCoarsePermissionGranted) {
// Permission missing on manifest
- Logging.info("Location permissions not added on AndroidManifest file < M")
+ Logging.error("Location permissions not added on AndroidManifest file < M")
return@withContext false
}
diff --git a/OneSignalSDK/onesignal/location/src/main/java/com/onesignal/location/internal/controller/impl/HmsLocationController.kt b/OneSignalSDK/onesignal/location/src/main/java/com/onesignal/location/internal/controller/impl/HmsLocationController.kt
index 19c11038ad..e2e219fcd5 100644
--- a/OneSignalSDK/onesignal/location/src/main/java/com/onesignal/location/internal/controller/impl/HmsLocationController.kt
+++ b/OneSignalSDK/onesignal/location/src/main/java/com/onesignal/location/internal/controller/impl/HmsLocationController.kt
@@ -50,7 +50,7 @@ internal class HmsLocationController(
hmsFusedLocationClient =
com.huawei.hms.location.LocationServices.getFusedLocationProviderClient(_applicationService.appContext)
} catch (e: Exception) {
- Logging.warn("Huawei LocationServices getFusedLocationProviderClient failed! $e")
+ Logging.error("Huawei LocationServices getFusedLocationProviderClient failed! $e")
wasSuccessful = false
return@withLock
}
@@ -75,7 +75,7 @@ internal class HmsLocationController(
},
)
.addOnFailureListener { e ->
- Logging.warn("Huawei LocationServices getLastLocation failed!", e)
+ Logging.error("Huawei LocationServices getLastLocation failed!", e)
waiter.wake(false)
}
wasSuccessful = waiter.waitForWake()
@@ -133,7 +133,7 @@ internal class HmsLocationController(
},
)
.addOnFailureListener { e ->
- Logging.warn("Huawei LocationServices getLastLocation failed!", e)
+ Logging.error("Huawei LocationServices getLastLocation failed!", e)
waiter.wake()
}
waiter.waitForWake()
diff --git a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/bridges/OneSignalHmsEventBridge.kt b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/bridges/OneSignalHmsEventBridge.kt
index d4599af876..7a9a8fef54 100644
--- a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/bridges/OneSignalHmsEventBridge.kt
+++ b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/bridges/OneSignalHmsEventBridge.kt
@@ -87,7 +87,7 @@ object OneSignalHmsEventBridge {
data = messageDataJSON.toString()
} catch (e: JSONException) {
- Logging.warn("OneSignalHmsEventBridge error when trying to create RemoteMessage data JSON")
+ Logging.error("OneSignalHmsEventBridge error when trying to create RemoteMessage data JSON")
}
// HMS notification with Message Type being Message won't trigger Activity reverse trampolining logic
diff --git a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/channels/impl/NotificationChannelManager.kt b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/channels/impl/NotificationChannelManager.kt
index 45577fc7c9..f5e9fe6e74 100644
--- a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/channels/impl/NotificationChannelManager.kt
+++ b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/channels/impl/NotificationChannelManager.kt
@@ -116,7 +116,7 @@ internal class NotificationChannelManager(
ledColor = BigInteger(ledc, 16)
channel.lightColor = ledColor.toInt()
} catch (t: Throwable) {
- Logging.warn("Couldn't convert ARGB Hex value to BigInteger:", t)
+ Logging.error("Couldn't convert ARGB Hex value to BigInteger:", t)
}
}
channel.enableLights(payload.optInt("led", 1) == 1)
@@ -211,7 +211,7 @@ internal class NotificationChannelManager(
} catch (e: NullPointerException) {
// Catch issue caused by "Attempt to invoke virtual method 'boolean android.app.NotificationChannel.isDeleted()' on a null object reference"
// https://github.com/OneSignal/OneSignal-Android-SDK/issues/1291
- Logging.warn("Error when trying to delete notification channel: " + e.message)
+ Logging.error("Error when trying to delete notification channel: " + e.message)
}
// Delete old channels - Payload will include all changes for the app. Any extra OS_ ones must
diff --git a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/common/OSWorkManagerHelper.kt b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/common/OSWorkManagerHelper.kt
index 9dfab0e6d7..a0c9397984 100644
--- a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/common/OSWorkManagerHelper.kt
+++ b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/common/OSWorkManagerHelper.kt
@@ -26,7 +26,7 @@ object OSWorkManagerHelper {
This aims to catch the IllegalStateException "WorkManager is not initialized properly..." -
https://androidx.tech/artifacts/work/work-runtime/2.8.1-source/androidx/work/impl/WorkManagerImpl.java.html
*/
- Logging.warn("OSWorkManagerHelper.getInstance failed, attempting to initialize: ", e)
+ Logging.error("OSWorkManagerHelper.getInstance failed, attempting to initialize: ", e)
initializeWorkManager(context)
WorkManager.getInstance(context)
}
@@ -51,7 +51,7 @@ object OSWorkManagerHelper {
1. We lost the race with another call to WorkManager.initialize outside of OneSignal.
2. It is possible for some other unexpected error is thrown from WorkManager.
*/
- Logging.warn("OSWorkManagerHelper initializing WorkManager failed: ", e)
+ Logging.error("OSWorkManagerHelper initializing WorkManager failed: ", e)
}
}
}
diff --git a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/data/impl/NotificationRepository.kt b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/data/impl/NotificationRepository.kt
index 056540cac9..66c750e3c0 100644
--- a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/data/impl/NotificationRepository.kt
+++ b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/data/impl/NotificationRepository.kt
@@ -429,7 +429,7 @@ internal class NotificationRepository(
}
}
} catch (t: Throwable) {
- Logging.warn("Error clearing oldest notifications over limit! ", t)
+ Logging.error("Error clearing oldest notifications over limit! ", t)
}
}
}
diff --git a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/generation/impl/NotificationGenerationProcessor.kt b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/generation/impl/NotificationGenerationProcessor.kt
index 2fe22d6aa8..86ac61fe58 100644
--- a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/generation/impl/NotificationGenerationProcessor.kt
+++ b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/generation/impl/NotificationGenerationProcessor.kt
@@ -85,9 +85,9 @@ internal class NotificationGenerationProcessor(
}.join()
}
} catch (to: TimeoutCancellationException) {
- Logging.info("remoteNotificationReceived timed out, continuing with wantsToDisplay=$wantsToDisplay.", to)
+ Logging.error("remoteNotificationReceived timed out, continuing with wantsToDisplay=$wantsToDisplay.", to)
} catch (t: Throwable) {
- Logging.info("remoteNotificationReceived threw an exception. Displaying normal OneSignal notification.", t)
+ Logging.error("remoteNotificationReceived threw an exception. Displaying normal OneSignal notification.", t)
}
var shouldDisplay =
@@ -120,7 +120,7 @@ internal class NotificationGenerationProcessor(
} catch (to: TimeoutCancellationException) {
Logging.info("notificationWillShowInForegroundHandler timed out, continuing with wantsToDisplay=$wantsToDisplay.", to)
} catch (t: Throwable) {
- Logging.info(
+ Logging.error(
"notificationWillShowInForegroundHandler threw an exception. Displaying normal OneSignal notification.",
t,
)
diff --git a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/lifecycle/impl/NotificationLifecycleService.kt b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/lifecycle/impl/NotificationLifecycleService.kt
index 1235267aba..eb4b3cac52 100644
--- a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/lifecycle/impl/NotificationLifecycleService.kt
+++ b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/lifecycle/impl/NotificationLifecycleService.kt
@@ -149,7 +149,7 @@ internal class NotificationLifecycleService(
deviceType,
)
} catch (ex: BackendException) {
- Logging.info("Notification opened confirmation failed with statusCode: ${ex.statusCode} response: ${ex.response}")
+ Logging.error("Notification opened confirmation failed with statusCode: ${ex.statusCode} response: ${ex.response}")
}
}
}
@@ -266,17 +266,20 @@ internal class NotificationLifecycleService(
val intent = intentGenerator.getIntentVisible()
if (intent != null) {
- Logging.debug("SDK running startActivity with Intent: $intent")
+ Logging.info("SDK running startActivity with Intent: $intent")
activity.startActivity(intent)
} else {
- Logging.debug("SDK not showing an Activity automatically due to it's settings.")
+ Logging.info("SDK not showing an Activity automatically due to it's settings.")
}
} catch (e: JSONException) {
- Logging.error("Could not parse JSON to open notification activity.", e)
+ Logging.error("Could not parse JSON to open notification activity.")
+ e.printStackTrace()
} catch (e: ActivityNotFoundException) {
- Logging.warn("No activity found to handle notification open intent.", e)
+ Logging.error("No activity found to handle notification open intent.")
+ e.printStackTrace()
} catch (e: Exception) {
- Logging.error("Could not open notification activity.", e)
+ Logging.error("Could not open notification activity.")
+ e.printStackTrace()
}
}
}
diff --git a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/pushtoken/PushTokenManager.kt b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/pushtoken/PushTokenManager.kt
index 5813d156bb..4dffeec5c5 100644
--- a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/pushtoken/PushTokenManager.kt
+++ b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/pushtoken/PushTokenManager.kt
@@ -18,11 +18,11 @@ internal class PushTokenManager(
override suspend fun retrievePushToken(): PushTokenResponse {
when (_deviceService.jetpackLibraryStatus) {
IDeviceService.JetpackLibraryStatus.MISSING -> {
- Logging.info("Could not find the Jetpack/AndroidX. Please make sure it has been correctly added to your project.")
+ Logging.fatal("Could not find the Jetpack/AndroidX. Please make sure it has been correctly added to your project.")
pushTokenStatus = SubscriptionStatus.MISSING_JETPACK_LIBRARY
}
IDeviceService.JetpackLibraryStatus.OUTDATED -> {
- Logging.info(
+ Logging.fatal(
"The included Jetpack/AndroidX Library is too old or incomplete.",
)
pushTokenStatus = SubscriptionStatus.OUTDATED_JETPACK_LIBRARY
diff --git a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/receivereceipt/impl/ReceiveReceiptProcessor.kt b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/receivereceipt/impl/ReceiveReceiptProcessor.kt
index e15937d1d3..ad7827d11a 100644
--- a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/receivereceipt/impl/ReceiveReceiptProcessor.kt
+++ b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/receivereceipt/impl/ReceiveReceiptProcessor.kt
@@ -20,7 +20,7 @@ internal class ReceiveReceiptProcessor(
try {
_backend.updateNotificationAsReceived(appId, notificationId, subscriptionId, deviceType)
} catch (ex: BackendException) {
- Logging.info("Receive receipt failed with statusCode: ${ex.statusCode} response: ${ex.response}")
+ Logging.error("Receive receipt failed with statusCode: ${ex.statusCode} response: ${ex.response}")
}
}
}
diff --git a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/registration/impl/PushRegistratorADM.kt b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/registration/impl/PushRegistratorADM.kt
index 6970196777..98d1611302 100644
--- a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/registration/impl/PushRegistratorADM.kt
+++ b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/registration/impl/PushRegistratorADM.kt
@@ -38,13 +38,13 @@ internal class PushRegistratorADM(
result =
if (registrationId != null) {
- Logging.debug("ADM registered with ID:$registrationId")
+ Logging.error("ADM registered with ID:$registrationId")
IPushRegistrator.RegisterResult(
registrationId,
SubscriptionStatus.SUBSCRIBED,
)
} else {
- Logging.info("com.onesignal.ADMMessageHandler timed out, please check that your have the receiver, service, and your package name matches(NOTE: Case Sensitive) per the OneSignal instructions.")
+ Logging.error("com.onesignal.ADMMessageHandler timed out, please check that your have the receiver, service, and your package name matches(NOTE: Case Sensitive) per the OneSignal instructions.")
IPushRegistrator.RegisterResult(
null,
SubscriptionStatus.ERROR,
diff --git a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/registration/impl/PushRegistratorAbstractGoogle.kt b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/registration/impl/PushRegistratorAbstractGoogle.kt
index ac24ca3558..d1d53fcdc6 100644
--- a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/registration/impl/PushRegistratorAbstractGoogle.kt
+++ b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/registration/impl/PushRegistratorAbstractGoogle.kt
@@ -57,12 +57,12 @@ internal abstract class PushRegistratorAbstractGoogle(
}
if (!_deviceService.hasFCMLibrary) {
- Logging.warn("The Firebase FCM library is missing! Please make sure to include it in your project.")
+ Logging.fatal("The Firebase FCM library is missing! Please make sure to include it in your project.")
return IPushRegistrator.RegisterResult(null, SubscriptionStatus.MISSING_FIREBASE_FCM_LIBRARY)
}
return if (!isValidProjectNumber(_configModelStore.model.googleProjectNumber)) {
- Logging.warn(
+ Logging.error(
"Missing Google Project number!\nPlease enter a Google Project number / Sender ID on under App Settings > Android > Configuration on the OneSignal dashboard.",
)
IPushRegistrator.RegisterResult(
@@ -84,14 +84,14 @@ internal abstract class PushRegistratorAbstractGoogle(
registerInBackground(senderId)
} else {
_upgradePrompt.showUpdateGPSDialog()
- Logging.warn("'Google Play services' app not installed or disabled on the device.")
+ Logging.error("'Google Play services' app not installed or disabled on the device.")
IPushRegistrator.RegisterResult(
null,
SubscriptionStatus.OUTDATED_GOOGLE_PLAY_SERVICES_APP,
)
}
} catch (t: Throwable) {
- Logging.warn(
+ Logging.error(
"Could not register with $providerName due to an issue with your AndroidManifest.xml or with 'Google Play services'.",
t,
)
@@ -140,7 +140,7 @@ internal abstract class PushRegistratorAbstractGoogle(
// Wrapping with new Exception so the current line is included in the stack trace.
val exception = Exception(e)
if (currentRetry >= REGISTRATION_RETRY_COUNT - 1) {
- Logging.info("Retry count of $REGISTRATION_RETRY_COUNT exceed! Could not get a $providerName Token.", exception)
+ Logging.error("Retry count of $REGISTRATION_RETRY_COUNT exceed! Could not get a $providerName Token.", exception)
} else {
Logging.info("'Google Play services' returned $exceptionMessage error. Current retry count: $currentRetry", exception)
@@ -152,12 +152,12 @@ internal abstract class PushRegistratorAbstractGoogle(
} else {
// Wrapping with new Exception so the current line is included in the stack trace.
val exception = Exception(e)
- Logging.warn("Error Getting $providerName Token", exception)
+ Logging.error("Error Getting $providerName Token", exception)
return IPushRegistrator.RegisterResult(null, pushStatus)
}
} catch (t: Throwable) {
- Logging.warn("Unknown error getting $providerName Token", t)
+ Logging.error("Unknown error getting $providerName Token", t)
return IPushRegistrator.RegisterResult(
null,
SubscriptionStatus.FIREBASE_FCM_ERROR_MISC_EXCEPTION,
diff --git a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/registration/impl/PushRegistratorHMS.kt b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/registration/impl/PushRegistratorHMS.kt
index d637242cdc..b568c34e9d 100644
--- a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/registration/impl/PushRegistratorHMS.kt
+++ b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/registration/impl/PushRegistratorHMS.kt
@@ -78,13 +78,13 @@ internal class PushRegistratorHMS(
}
return if (pushToken != null) {
- Logging.debug("HMS registered with ID:$pushToken")
+ Logging.error("HMS registered with ID:$pushToken")
IPushRegistrator.RegisterResult(
pushToken,
SubscriptionStatus.SUBSCRIBED,
)
} else {
- Logging.warn("HmsMessageServiceOneSignal.onNewToken timed out.")
+ Logging.error("HmsMessageServiceOneSignal.onNewToken timed out.")
IPushRegistrator.RegisterResult(
null,
SubscriptionStatus.HMS_TOKEN_TIMEOUT,
diff --git a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/restoration/impl/NotificationRestoreProcessor.kt b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/restoration/impl/NotificationRestoreProcessor.kt
index bc2ba38c7d..d00de6ce8d 100644
--- a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/restoration/impl/NotificationRestoreProcessor.kt
+++ b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/restoration/impl/NotificationRestoreProcessor.kt
@@ -30,7 +30,7 @@ internal class NotificationRestoreProcessor(
_badgeCountUpdater.update()
} catch (t: Throwable) {
- Logging.warn("Error restoring notification records! ", t)
+ Logging.error("Error restoring notification records! ", t)
}
}
diff --git a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/restoration/impl/NotificationRestoreWorkManager.kt b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/restoration/impl/NotificationRestoreWorkManager.kt
index 5a95221ccd..bb2b567850 100644
--- a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/restoration/impl/NotificationRestoreWorkManager.kt
+++ b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/restoration/impl/NotificationRestoreWorkManager.kt
@@ -16,14 +16,13 @@ internal class NotificationRestoreWorkManager : INotificationRestoreWorkManager
// Notifications will never be force removed when the app's process is running,
// so we only need to restore at most once per cold start of the app.
private var restored = false
- private val lock = Any()
override fun beginEnqueueingWork(
context: Context,
shouldDelay: Boolean,
) {
// Only allow one piece of work to be enqueued.
- synchronized(lock) {
+ synchronized(restored) {
if (restored) {
return
}
diff --git a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/services/ADMMessageHandler.kt b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/services/ADMMessageHandler.kt
index 5434bb13d7..cc8d9c2e2e 100644
--- a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/services/ADMMessageHandler.kt
+++ b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/services/ADMMessageHandler.kt
@@ -34,10 +34,10 @@ class ADMMessageHandler : ADMMessageHandlerBase("ADMMessageHandler") {
}
override fun onRegistrationError(error: String) {
- Logging.info("ADM:onRegistrationError: $error")
+ Logging.error("ADM:onRegistrationError: $error")
if ("INVALID_SENDER" == error) {
- Logging.info(
+ Logging.error(
"Please double check that you have a matching package name (NOTE: Case Sensitive), api_key.txt, and the apk was signed with the same Keystore and Alias.",
)
}
diff --git a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/services/ADMMessageHandlerJob.kt b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/services/ADMMessageHandlerJob.kt
index f309538d23..c707333743 100644
--- a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/services/ADMMessageHandlerJob.kt
+++ b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/services/ADMMessageHandlerJob.kt
@@ -50,9 +50,9 @@ class ADMMessageHandlerJob : ADMMessageHandlerJobBase() {
context: Context?,
error: String?,
) {
- Logging.info("ADM:onRegistrationError: $error")
+ Logging.error("ADM:onRegistrationError: $error")
if ("INVALID_SENDER" == error) {
- Logging.info(
+ Logging.error(
"Please double check that you have a matching package name (NOTE: Case Sensitive), api_key.txt, and the apk was signed with the same Keystore and Alias.",
)
}
diff --git a/OneSignalSDK/onesignal/otel/.gitignore b/OneSignalSDK/onesignal/otel/.gitignore
deleted file mode 100644
index 796b96d1c4..0000000000
--- a/OneSignalSDK/onesignal/otel/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
diff --git a/OneSignalSDK/onesignal/otel/build.gradle b/OneSignalSDK/onesignal/otel/build.gradle
deleted file mode 100644
index 7860f04201..0000000000
--- a/OneSignalSDK/onesignal/otel/build.gradle
+++ /dev/null
@@ -1,67 +0,0 @@
-plugins {
- id 'com.android.library'
- id 'kotlin-android'
- id 'com.diffplug.spotless'
- id 'io.gitlab.arturbosch.detekt'
-}
-
-android {
- namespace 'com.onesignal.otel'
- compileSdkVersion rootProject.buildVersions.compileSdkVersion
-
- defaultConfig {
- minSdkVersion 26
- consumerProguardFiles "consumer-rules.pro"
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- }
-
- buildTypes {
- original {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- }
- release {
- minifyEnabled false
- }
- unity {
- minifyEnabled false
- }
- }
-
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
-
- kotlinOptions {
- jvmTarget = '1.8'
- freeCompilerArgs += ['-module-name', namespace]
- }
-}
-
-ext {
- projectName = "OneSignal SDK Otel"
- projectDescription = "OneSignal Android SDK - OpenTelemetry Module"
-}
-
-dependencies {
- implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
- implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion"
-
- implementation platform("io.opentelemetry:opentelemetry-bom:$rootProject.opentelemetryBomVersion")
- implementation('io.opentelemetry:opentelemetry-api')
- implementation('io.opentelemetry:opentelemetry-sdk')
- implementation('io.opentelemetry:opentelemetry-exporter-otlp')
- implementation("io.opentelemetry.semconv:opentelemetry-semconv:$rootProject.opentelemetrySemconvVersion")
- implementation("io.opentelemetry.contrib:opentelemetry-disk-buffering:$rootProject.opentelemetryDiskBufferingVersion")
-
- testImplementation(project(':OneSignal:testhelpers'))
- testImplementation("io.kotest:kotest-runner-junit5:$kotestVersion")
- testImplementation("io.kotest:kotest-runner-junit5-jvm:$kotestVersion")
- testImplementation("io.kotest:kotest-assertions-core:$kotestVersion")
- testImplementation("io.mockk:mockk:$ioMockVersion")
- testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion")
-}
-
-apply from: '../detekt.gradle'
-apply from: '../spotless.gradle'
diff --git a/OneSignalSDK/onesignal/otel/consumer-rules.pro b/OneSignalSDK/onesignal/otel/consumer-rules.pro
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/OneSignalSDK/onesignal/otel/proguard-rules.pro b/OneSignalSDK/onesignal/otel/proguard-rules.pro
deleted file mode 100644
index f1b424510d..0000000000
--- a/OneSignalSDK/onesignal/otel/proguard-rules.pro
+++ /dev/null
@@ -1,21 +0,0 @@
-# Add project specific ProGuard rules here.
-# You can control the set of applied configuration files using the
-# proguardFiles setting in build.gradle.
-#
-# For more details, see
-# http://developer.android.com/guide/developing/tools/proguard.html
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-# public *;
-#}
-
-# Uncomment this to preserve the line number information for
-# debugging stack traces.
-#-keepattributes SourceFile,LineNumberTable
-
-# If you keep the line number information, uncomment this to
-# hide the original source file name.
-#-renamesourcefileattribute SourceFile
diff --git a/OneSignalSDK/onesignal/otel/src/main/AndroidManifest.xml b/OneSignalSDK/onesignal/otel/src/main/AndroidManifest.xml
deleted file mode 100644
index 8bdb7e14b3..0000000000
--- a/OneSignalSDK/onesignal/otel/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/IOtelCrashHandler.kt b/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/IOtelCrashHandler.kt
deleted file mode 100644
index 93b31fc75f..0000000000
--- a/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/IOtelCrashHandler.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.onesignal.otel
-
-/**
- * Platform-agnostic crash handler interface.
- * This should be initialized as early as possible and be independent of service architecture.
- */
-interface IOtelCrashHandler {
- /**
- * Initialize the crash handler. This should be called as early as possible,
- * before any other initialization that might crash.
- */
- fun initialize()
-
- /**
- * Unregisters this crash handler, restoring the previous default handler.
- * Safe to call even if [initialize] was never called (no-op in that case).
- */
- fun unregister()
-}
diff --git a/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/IOtelCrashReporter.kt b/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/IOtelCrashReporter.kt
deleted file mode 100644
index 4ab4a7e8b6..0000000000
--- a/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/IOtelCrashReporter.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.onesignal.otel
-
-/**
- * Platform-agnostic crash reporter interface.
- */
-interface IOtelCrashReporter {
- suspend fun saveCrash(thread: Thread, throwable: Throwable)
-}
diff --git a/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/IOtelLogger.kt b/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/IOtelLogger.kt
deleted file mode 100644
index 510ffab2eb..0000000000
--- a/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/IOtelLogger.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-package com.onesignal.otel
-
-/**
- * Platform-agnostic logger interface for the Otel module.
- * Implementations should be provided by the platform (Android/iOS).
- */
-interface IOtelLogger {
- /**
- * Logs an error message.
- *
- * @param message The error message to log
- */
- fun error(message: String)
-
- /**
- * Logs a warning message.
- *
- * @param message The warning message to log
- */
- fun warn(message: String)
-
- /**
- * Logs an informational message.
- *
- * @param message The info message to log
- */
- fun info(message: String)
-
- /**
- * Logs a debug message.
- *
- * @param message The debug message to log
- */
- fun debug(message: String)
-}
diff --git a/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/IOtelOpenTelemetry.kt b/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/IOtelOpenTelemetry.kt
deleted file mode 100644
index 156df29ffd..0000000000
--- a/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/IOtelOpenTelemetry.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-package com.onesignal.otel
-
-import io.opentelemetry.api.logs.LogRecordBuilder
-import io.opentelemetry.sdk.common.CompletableResultCode
-import io.opentelemetry.sdk.logs.export.LogRecordExporter
-
-/**
- * Platform-agnostic OpenTelemetry interface.
- */
-interface IOtelOpenTelemetry {
- /**
- * Gets a LogRecordBuilder for creating log records.
- * This is a suspend function as it may need to initialize the SDK on first call.
- *
- * @return A LogRecordBuilder instance for building log records
- */
- suspend fun getLogger(): LogRecordBuilder
-
- /**
- * Forces a flush of all pending log records.
- * This ensures all buffered logs are exported immediately.
- *
- * @return A CompletableResultCode indicating the flush operation result
- */
- suspend fun forceFlush(): CompletableResultCode
-
- /**
- * Shuts down the underlying OpenTelemetry SDK, flushing pending data
- * and releasing resources (exporters, logger providers, etc.).
- * After this call the instance must not be reused.
- */
- fun shutdown()
-}
-
-/**
- * Interface for crash-specific OpenTelemetry (local file storage).
- */
-interface IOtelOpenTelemetryCrash : IOtelOpenTelemetry
-
-/**
- * Interface for remote OpenTelemetry (network export).
- */
-interface IOtelOpenTelemetryRemote : IOtelOpenTelemetry {
- val logExporter: LogRecordExporter
-}
diff --git a/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/IOtelPlatformProvider.kt b/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/IOtelPlatformProvider.kt
deleted file mode 100644
index 98978ee19b..0000000000
--- a/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/IOtelPlatformProvider.kt
+++ /dev/null
@@ -1,64 +0,0 @@
-package com.onesignal.otel
-
-/**
- * Platform-agnostic provider interface for injecting platform-specific values.
- * All Android/iOS specific values should be provided through this interface.
- */
-interface IOtelPlatformProvider {
- // Top-level attributes (static, calculated once)
- /**
- * Gets the installation ID for this device.
- * This is an async operation as it may need to generate a new ID if one doesn't exist.
- *
- * @return The installation ID as a string
- */
- suspend fun getInstallId(): String
- val sdkBase: String
- val sdkBaseVersion: String
- val appPackageId: String
- val appVersion: String
- val deviceManufacturer: String
- val deviceModel: String
- val osName: String
- val osVersion: String
- val osBuildId: String
- val sdkWrapper: String?
- val sdkWrapperVersion: String?
-
- // Per-event attributes (dynamic, calculated per event)
- val appId: String?
- val onesignalId: String?
- val pushSubscriptionId: String?
- val appState: String // "foreground" or "background"
- val processUptime: Long // in milliseconds
- val currentThreadName: String
-
- // Crash-specific configuration
- val crashStoragePath: String
- val minFileAgeForReadMillis: Long
-
- // Remote logging configuration
- /**
- * Whether remote logging (crash reporting, ANR detection, remote log shipping) is enabled.
- * Derived from the presence of a valid log_level in logging_config:
- * - "logging_config": {} → false (not on allowlist)
- * - "logging_config": {"log_level": "ERROR"} → true (on allowlist)
- * Defaults to false on first launch (before remote config is cached).
- */
- val isRemoteLoggingEnabled: Boolean
-
- /**
- * The minimum log level to send remotely as a string (e.g., "ERROR", "WARN").
- * Null when logging_config is empty or not yet cached (disabled).
- * Valid values: "NONE", "FATAL", "ERROR", "WARN", "INFO", "DEBUG", "VERBOSE"
- */
- val remoteLogLevel: String?
- val appIdForHeaders: String
-
- /**
- * Base URL for the OneSignal API (e.g. "https://api.onesignal.com").
- * The Otel exporter appends "sdk/otel/v1/logs" to this.
- * Sourced from the core module so all SDK traffic hits the same host.
- */
- val apiBaseUrl: String
-}
diff --git a/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/OneSignalOpenTelemetry.kt b/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/OneSignalOpenTelemetry.kt
deleted file mode 100644
index 3e470f9422..0000000000
--- a/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/OneSignalOpenTelemetry.kt
+++ /dev/null
@@ -1,148 +0,0 @@
-package com.onesignal.otel
-
-import com.onesignal.otel.attributes.OtelFieldsPerEvent
-import com.onesignal.otel.attributes.OtelFieldsTopLevel
-import com.onesignal.otel.config.OtelConfigCrashFile
-import com.onesignal.otel.config.OtelConfigRemoteOneSignal
-import com.onesignal.otel.config.OtelConfigShared
-import io.opentelemetry.api.logs.LogRecordBuilder
-import io.opentelemetry.sdk.OpenTelemetrySdk
-import io.opentelemetry.sdk.common.CompletableResultCode
-import java.util.concurrent.TimeUnit
-import kotlin.coroutines.resume
-import kotlin.coroutines.suspendCoroutine
-
-internal fun LogRecordBuilder.setAllAttributes(attributes: Map): LogRecordBuilder {
- attributes.forEach { this.setAttribute(it.key, it.value) }
- return this
-}
-
-/**
- * Extension function to set all attributes from an Attributes object.
- * Made public so it can be used from other modules (e.g., core module for logging).
- */
-fun LogRecordBuilder.setAllAttributes(attributes: io.opentelemetry.api.common.Attributes): LogRecordBuilder {
- attributes.forEach { key, value ->
- val keyString = key.key
- when (value) {
- is String -> this.setAttribute(keyString, value)
- is Long -> this.setAttribute(keyString, value)
- is Double -> this.setAttribute(keyString, value)
- is Boolean -> this.setAttribute(keyString, value)
- else -> this.setAttribute(keyString, value.toString())
- }
- }
- return this
-}
-
-internal abstract class OneSignalOpenTelemetryBase(
- private val osTopLevelFields: OtelFieldsTopLevel,
- private val osPerEventFields: OtelFieldsPerEvent,
-) : IOtelOpenTelemetry {
- private val lock = Any()
- private var sdkCachedValue: OpenTelemetrySdk? = null
-
- protected suspend fun getSdk(): OpenTelemetrySdk {
- val attributes = osTopLevelFields.getAttributes()
- synchronized(lock) {
- var localSdk = sdkCachedValue
- if (localSdk != null) {
- return localSdk
- }
-
- localSdk = getSdkInstance(attributes)
- sdkCachedValue = localSdk
- return localSdk
- }
- }
-
- protected abstract fun getSdkInstance(attributes: Map): OpenTelemetrySdk
-
- override suspend fun forceFlush(): CompletableResultCode {
- val sdkLoggerProvider = getSdk().sdkLoggerProvider
- return suspendCoroutine {
- it.resume(
- sdkLoggerProvider.forceFlush().join(FORCE_FLUSH_TIMEOUT_SECONDS, TimeUnit.SECONDS)
- )
- }
- }
-
- @Suppress("TooGenericExceptionCaught")
- override fun shutdown() {
- synchronized(lock) {
- try {
- sdkCachedValue?.shutdown()
- } catch (_: Throwable) {
- // Best-effort cleanup — never propagate Otel teardown failures
- }
- sdkCachedValue = null
- }
- }
-
- companion object {
- private const val FORCE_FLUSH_TIMEOUT_SECONDS = 10L
- }
-
- override suspend fun getLogger(): LogRecordBuilder =
- getSdk()
- .sdkLoggerProvider
- .loggerBuilder("loggerBuilder")
- .build()
- .logRecordBuilder()
- .setAllAttributes(osPerEventFields.getAttributes())
-}
-
-internal class OneSignalOpenTelemetryRemote(
- private val platformProvider: IOtelPlatformProvider,
- osTopLevelFields: OtelFieldsTopLevel,
- osPerEventFields: OtelFieldsPerEvent,
-) : OneSignalOpenTelemetryBase(osTopLevelFields, osPerEventFields),
- IOtelOpenTelemetryRemote {
-
- private val appId: String get() = platformProvider.appIdForHeaders
-
- val extraHttpHeaders: Map by lazy {
- mapOf(
- "X-OneSignal-App-Id" to appId,
- "X-OneSignal-SDK-Version" to platformProvider.sdkBaseVersion,
- )
- }
-
- private val apiBaseUrl: String get() = platformProvider.apiBaseUrl
-
- override val logExporter by lazy {
- OtelConfigRemoteOneSignal.HttpRecordBatchExporter.create(extraHttpHeaders, appId, apiBaseUrl)
- }
-
- override fun getSdkInstance(attributes: Map): OpenTelemetrySdk =
- OpenTelemetrySdk
- .builder()
- .setLoggerProvider(
- OtelConfigRemoteOneSignal.SdkLoggerProviderConfig.create(
- OtelConfigShared.ResourceConfig.create(attributes),
- extraHttpHeaders,
- appId,
- apiBaseUrl,
- )
- ).build()
-}
-
-internal class OneSignalOpenTelemetryCrashLocal(
- private val platformProvider: IOtelPlatformProvider,
- osTopLevelFields: OtelFieldsTopLevel,
- osPerEventFields: OtelFieldsPerEvent,
-) : OneSignalOpenTelemetryBase(osTopLevelFields, osPerEventFields),
- IOtelOpenTelemetryCrash {
- override fun getSdkInstance(attributes: Map): OpenTelemetrySdk =
- OpenTelemetrySdk
- .builder()
- .setLoggerProvider(
- OtelConfigCrashFile.SdkLoggerProviderConfig.create(
- OtelConfigShared.ResourceConfig.create(
- attributes
- ),
- platformProvider.crashStoragePath,
- platformProvider.minFileAgeForReadMillis,
- )
- ).build()
-}
diff --git a/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/OtelFactory.kt b/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/OtelFactory.kt
deleted file mode 100644
index c4e46e6630..0000000000
--- a/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/OtelFactory.kt
+++ /dev/null
@@ -1,112 +0,0 @@
-package com.onesignal.otel
-
-import com.onesignal.otel.attributes.OtelFieldsPerEvent
-import com.onesignal.otel.attributes.OtelFieldsTopLevel
-import com.onesignal.otel.crash.OtelCrashHandler
-import com.onesignal.otel.crash.OtelCrashReporter
-import com.onesignal.otel.crash.OtelCrashUploader
-
-/**
- * Factory class for creating Otel components.
- * This allows for fast initialization of the crash handler with all dependencies
- * pre-populated.
- */
-object OtelFactory {
- /**
- * Creates a fully configured crash handler that can be initialized immediately.
- * All fields are pre-populated for fast initialization.
- *
- * This method composes other factory methods to create the crash handler,
- * ensuring consistency and reducing duplication.
- */
- fun createCrashHandler(
- platformProvider: IOtelPlatformProvider,
- logger: IOtelLogger,
- ): IOtelCrashHandler {
- val crashLocal = createCrashLocalTelemetry(platformProvider)
- val crashReporter = createCrashReporter(crashLocal, logger)
- return OtelCrashHandler(crashReporter, logger)
- }
-
- /**
- * Creates a crash uploader for sending crash reports to the server.
- *
- * This is platform-agnostic and can be used in KMP projects.
- * All platform-specific values must be provided through IOtelPlatformProvider.
- *
- * @param platformProvider Platform-specific provider that injects all required values
- * @param logger Platform-specific logger implementation
- * @return Platform-agnostic crash uploader that can be used on Android/iOS
- */
- fun createCrashUploader(
- platformProvider: IOtelPlatformProvider,
- logger: IOtelLogger,
- ): OtelCrashUploader {
- val topLevelFields = OtelFieldsTopLevel(platformProvider)
- val perEventFields = OtelFieldsPerEvent(platformProvider)
- val remote = OneSignalOpenTelemetryRemote(
- platformProvider,
- topLevelFields,
- perEventFields
- )
- return OtelCrashUploader(remote, platformProvider, logger)
- }
-
- /**
- * Creates a remote OpenTelemetry instance for logging SDK events.
- *
- * This is platform-agnostic and can be used in KMP projects.
- * All platform-specific values must be provided through IOtelPlatformProvider.
- *
- * @param platformProvider Platform-specific provider that injects all required values
- * @return Platform-agnostic remote telemetry instance for logging
- */
- fun createRemoteTelemetry(
- platformProvider: IOtelPlatformProvider,
- ): IOtelOpenTelemetryRemote {
- val topLevelFields = OtelFieldsTopLevel(platformProvider)
- val perEventFields = OtelFieldsPerEvent(platformProvider)
- return OneSignalOpenTelemetryRemote(
- platformProvider,
- topLevelFields,
- perEventFields
- )
- }
-
- /**
- * Creates a local OpenTelemetry crash instance for saving crash reports locally.
- *
- * This is platform-agnostic and can be used in KMP projects.
- * All platform-specific values must be provided through IOtelPlatformProvider.
- *
- * @param platformProvider Platform-specific provider that injects all required values
- * @return Platform-agnostic crash local telemetry instance
- */
- fun createCrashLocalTelemetry(
- platformProvider: IOtelPlatformProvider,
- ): IOtelOpenTelemetryCrash {
- val topLevelFields = OtelFieldsTopLevel(platformProvider)
- val perEventFields = OtelFieldsPerEvent(platformProvider)
- return OneSignalOpenTelemetryCrashLocal(
- platformProvider,
- topLevelFields,
- perEventFields
- )
- }
-
- /**
- * Creates a crash reporter for saving crash reports.
- *
- * This is platform-agnostic and can be used in KMP projects.
- *
- * @param openTelemetryCrash The crash telemetry instance to use
- * @param logger Platform-specific logger implementation
- * @return Platform-agnostic crash reporter
- */
- fun createCrashReporter(
- openTelemetryCrash: IOtelOpenTelemetryCrash,
- logger: IOtelLogger,
- ): IOtelCrashReporter {
- return OtelCrashReporter(openTelemetryCrash, logger)
- }
-}
diff --git a/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/OtelLoggingHelper.kt b/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/OtelLoggingHelper.kt
deleted file mode 100644
index 8b1c85c7b0..0000000000
--- a/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/OtelLoggingHelper.kt
+++ /dev/null
@@ -1,65 +0,0 @@
-package com.onesignal.otel
-
-import io.opentelemetry.api.common.Attributes
-import io.opentelemetry.api.logs.Severity
-import java.time.Instant
-
-/**
- * Helper class for logging to Otel from the Logging class.
- * This abstracts away OpenTelemetry-specific types so the core module
- * doesn't need direct OpenTelemetry dependencies.
- */
-object OtelLoggingHelper {
- /**
- * Logs a message to Otel remote telemetry.
- * This method handles all OpenTelemetry-specific types internally.
- *
- * @param telemetry The Otel remote telemetry instance
- * @param level The log level as a string (VERBOSE, DEBUG, INFO, WARN, ERROR, FATAL)
- * @param message The log message
- * @param exceptionType Optional exception type
- * @param exceptionMessage Optional exception message
- * @param exceptionStacktrace Optional exception stacktrace
- */
- suspend fun logToOtel(
- telemetry: IOtelOpenTelemetryRemote,
- level: String,
- message: String,
- exceptionType: String? = null,
- exceptionMessage: String? = null,
- exceptionStacktrace: String? = null,
- ) {
- val severity = when (level.uppercase()) {
- "VERBOSE" -> Severity.TRACE
- "DEBUG" -> Severity.DEBUG
- "INFO" -> Severity.INFO
- "WARN" -> Severity.WARN
- "ERROR" -> Severity.ERROR
- "FATAL" -> Severity.FATAL
- else -> Severity.INFO
- }
-
- val attributes = Attributes.builder()
- .put("log.message", message)
- .put("log.level", level)
- .apply {
- if (exceptionType != null) {
- put("exception.type", exceptionType)
- }
- if (exceptionMessage != null) {
- put("exception.message", exceptionMessage)
- }
- if (exceptionStacktrace != null) {
- put("exception.stacktrace", exceptionStacktrace)
- }
- }
- .build()
-
- val logRecordBuilder = telemetry.getLogger()
- logRecordBuilder.setAllAttributes(attributes)
- logRecordBuilder.setSeverity(severity)
- logRecordBuilder.setBody(message)
- logRecordBuilder.setTimestamp(Instant.now())
- logRecordBuilder.emit()
- }
-}
diff --git a/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/attributes/OtelFieldsPerEvent.kt b/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/attributes/OtelFieldsPerEvent.kt
deleted file mode 100644
index 2d2cb20026..0000000000
--- a/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/attributes/OtelFieldsPerEvent.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-package com.onesignal.otel.attributes
-
-import com.onesignal.otel.IOtelPlatformProvider
-import com.squareup.wire.internal.toUnmodifiableMap
-import java.util.UUID
-
-/**
- * Purpose: Fields to be included in each individual Otel event.
- * These can change during runtime.
- */
-internal class OtelFieldsPerEvent(
- private val platformProvider: IOtelPlatformProvider,
-) {
- fun getAttributes(): Map {
- val attributes: MutableMap = mutableMapOf()
-
- attributes["log.record.uid"] = recordId.toString()
-
- attributes
- .putIfValueNotNull("ossdk.app_id", platformProvider.appId)
- .putIfValueNotNull("ossdk.onesignal_id", platformProvider.onesignalId)
- .putIfValueNotNull("ossdk.push_subscription_id", platformProvider.pushSubscriptionId)
-
- // Use platform-agnostic attribute name (works for both Android and iOS)
- attributes["app.state"] = platformProvider.appState
- attributes["process.uptime"] = platformProvider.processUptime.toString()
- attributes["thread.name"] = platformProvider.currentThreadName
-
- return attributes.toUnmodifiableMap()
- }
-
- // idempotency so the backend can filter on duplicate events
- // https://opentelemetry.io/docs/specs/semconv/general/logs/#general-log-identification-attributes
- private val recordId: UUID get() = UUID.randomUUID()
-}
diff --git a/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/attributes/OtelFieldsTopLevel.kt b/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/attributes/OtelFieldsTopLevel.kt
deleted file mode 100644
index 8021535f67..0000000000
--- a/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/attributes/OtelFieldsTopLevel.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-package com.onesignal.otel.attributes
-
-import com.onesignal.otel.IOtelPlatformProvider
-import com.squareup.wire.internal.toUnmodifiableMap
-
-/**
- * Purpose: Fields to be included in every Otel request that goes out.
- * Requirements: Only include fields that can NOT change during runtime,
- * as these are only fetched once. (Calculated fields are ok)
- */
-internal class OtelFieldsTopLevel(
- private val platformProvider: IOtelPlatformProvider,
-) {
- suspend fun getAttributes(): Map {
- val attributes: MutableMap =
- mutableMapOf(
- "ossdk.install_id" to platformProvider.getInstallId(),
- "ossdk.sdk_base" to platformProvider.sdkBase,
- "ossdk.sdk_base_version" to platformProvider.sdkBaseVersion,
- "ossdk.app_package_id" to platformProvider.appPackageId,
- "ossdk.app_version" to platformProvider.appVersion,
- "device.manufacturer" to platformProvider.deviceManufacturer,
- "device.model.identifier" to platformProvider.deviceModel,
- "os.name" to platformProvider.osName,
- "os.version" to platformProvider.osVersion,
- "os.build_id" to platformProvider.osBuildId,
- )
-
- attributes
- .putIfValueNotNull("ossdk.sdk_wrapper", platformProvider.sdkWrapper)
- .putIfValueNotNull("ossdk.sdk_wrapper_version", platformProvider.sdkWrapperVersion)
-
- return attributes.toUnmodifiableMap()
- }
-}
-
-internal fun MutableMap.putIfValueNotNull(key: K, value: V?): MutableMap {
- if (value != null) {
- this[key] = value
- }
- return this
-}
diff --git a/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/config/OtelConfigCrashFile.kt b/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/config/OtelConfigCrashFile.kt
deleted file mode 100644
index aa99748589..0000000000
--- a/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/config/OtelConfigCrashFile.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-package com.onesignal.otel.config
-
-import io.opentelemetry.contrib.disk.buffering.exporters.LogRecordToDiskExporter
-import io.opentelemetry.contrib.disk.buffering.storage.impl.FileLogRecordStorage
-import io.opentelemetry.contrib.disk.buffering.storage.impl.FileStorageConfiguration
-import io.opentelemetry.sdk.logs.SdkLoggerProvider
-import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor
-import java.io.File
-import kotlin.time.Duration.Companion.hours
-
-internal class OtelConfigCrashFile {
- internal object SdkLoggerProviderConfig {
- // NOTE: Only use such as small maxFileAgeForWrite for
- // crashes, as we want to send them as soon as possible
- // without having to wait too long for buffers.
- private const val MAX_FILE_AGE_FOR_WRITE_MILLIS = 2_000L
-
- fun getFileLogRecordStorage(
- rootDir: String,
- minFileAgeForReadMillis: Long
- ): FileLogRecordStorage =
- FileLogRecordStorage.create(
- File(rootDir),
- FileStorageConfiguration
- .builder()
- .setMaxFileAgeForWriteMillis(MAX_FILE_AGE_FOR_WRITE_MILLIS)
- .setMinFileAgeForReadMillis(minFileAgeForReadMillis)
- .setMaxFileAgeForReadMillis(72.hours.inWholeMilliseconds)
- .build()
- )
-
- fun create(
- resource: io.opentelemetry.sdk.resources.Resource,
- rootDir: String,
- minFileAgeForReadMillis: Long,
- ): SdkLoggerProvider {
- val logToDiskExporter =
- LogRecordToDiskExporter
- .builder(getFileLogRecordStorage(rootDir, minFileAgeForReadMillis))
- .build()
- return SdkLoggerProvider
- .builder()
- .setResource(resource)
- .addLogRecordProcessor(
- BatchLogRecordProcessor.builder(logToDiskExporter).build()
- ).setLogLimits(OtelConfigShared.LogLimitsConfig::logLimits)
- .build()
- }
- }
-}
diff --git a/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/config/OtelConfigRemoteOneSignal.kt b/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/config/OtelConfigRemoteOneSignal.kt
deleted file mode 100644
index b6d877dda8..0000000000
--- a/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/config/OtelConfigRemoteOneSignal.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-package com.onesignal.otel.config
-
-import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter
-import io.opentelemetry.sdk.logs.SdkLoggerProvider
-import io.opentelemetry.sdk.logs.export.LogRecordExporter
-import java.time.Duration
-
-internal class OtelConfigRemoteOneSignal {
- companion object {
- const val OTEL_PATH = "sdk/otel"
-
- fun buildEndpoint(apiBaseUrl: String, appId: String): String =
- "$apiBaseUrl$OTEL_PATH/v1/logs?app_id=$appId"
- }
-
- object LogRecordExporterConfig {
- private const val EXPORTER_TIMEOUT_SECONDS = 10L
-
- fun otlpHttpLogRecordExporter(
- headers: Map,
- endpoint: String,
- ): LogRecordExporter {
- val builder = OtlpHttpLogRecordExporter.builder()
- headers.forEach { builder.addHeader(it.key, it.value) }
- builder
- .setEndpoint(endpoint)
- .setTimeout(Duration.ofSeconds(EXPORTER_TIMEOUT_SECONDS))
- return builder.build()
- }
- }
-
- object SdkLoggerProviderConfig {
- fun create(
- resource: io.opentelemetry.sdk.resources.Resource,
- extraHttpHeaders: Map,
- appId: String,
- apiBaseUrl: String,
- ): SdkLoggerProvider =
- SdkLoggerProvider
- .builder()
- .setResource(resource)
- .addLogRecordProcessor(
- OtelConfigShared.LogRecordProcessorConfig.batchLogRecordProcessor(
- HttpRecordBatchExporter.create(extraHttpHeaders, appId, apiBaseUrl)
- )
- ).setLogLimits(OtelConfigShared.LogLimitsConfig::logLimits)
- .build()
- }
-
- object HttpRecordBatchExporter {
- fun create(extraHttpHeaders: Map, appId: String, apiBaseUrl: String) =
- LogRecordExporterConfig.otlpHttpLogRecordExporter(
- extraHttpHeaders,
- buildEndpoint(apiBaseUrl, appId)
- )
- }
-}
diff --git a/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/config/OtelConfigShared.kt b/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/config/OtelConfigShared.kt
deleted file mode 100644
index f54b3d5590..0000000000
--- a/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/config/OtelConfigShared.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.onesignal.otel.config
-
-import io.opentelemetry.sdk.logs.LogLimits
-import io.opentelemetry.sdk.logs.LogRecordProcessor
-import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor
-import io.opentelemetry.sdk.logs.export.LogRecordExporter
-import io.opentelemetry.sdk.resources.Resource
-import io.opentelemetry.sdk.resources.ResourceBuilder
-import io.opentelemetry.semconv.ServiceAttributes
-import java.time.Duration
-
-internal fun ResourceBuilder.putAll(attributes: Map): ResourceBuilder {
- attributes.forEach { this.put(it.key, it.value) }
- return this
-}
-
-internal class OtelConfigShared {
- object ResourceConfig {
- fun create(attributes: Map): Resource =
- Resource
- .getDefault()
- .toBuilder()
- .put(ServiceAttributes.SERVICE_NAME, "OneSignalDeviceSDK")
- .putAll(attributes)
- .build()
- }
-
- object LogRecordProcessorConfig {
- private const val MAX_QUEUE_SIZE = 100
- private const val MAX_EXPORT_BATCH_SIZE = 100
- private const val EXPORTER_TIMEOUT_SECONDS = 30L
- private const val SCHEDULE_DELAY_SECONDS = 1L
-
- fun batchLogRecordProcessor(logRecordExporter: LogRecordExporter): LogRecordProcessor =
- BatchLogRecordProcessor
- .builder(logRecordExporter)
- .setMaxQueueSize(MAX_QUEUE_SIZE)
- .setMaxExportBatchSize(MAX_EXPORT_BATCH_SIZE)
- .setExporterTimeout(Duration.ofSeconds(EXPORTER_TIMEOUT_SECONDS))
- .setScheduleDelay(Duration.ofSeconds(SCHEDULE_DELAY_SECONDS))
- .build()
- }
-
- object LogLimitsConfig {
- private const val MAX_NUMBER_OF_ATTRIBUTES = 128
-
- // We want a high value max length as the exception.stacktrace
- // value can be lengthly.
- private const val MAX_ATTRIBUTE_VALUE_LENGTH = 32000
-
- fun logLimits(): LogLimits =
- LogLimits
- .builder()
- .setMaxNumberOfAttributes(MAX_NUMBER_OF_ATTRIBUTES)
- .setMaxAttributeValueLength(MAX_ATTRIBUTE_VALUE_LENGTH)
- .build()
- }
-}
diff --git a/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/crash/IOtelAnrDetector.kt b/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/crash/IOtelAnrDetector.kt
deleted file mode 100644
index b7b5027ee8..0000000000
--- a/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/crash/IOtelAnrDetector.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.onesignal.otel.crash
-
-/**
- * Platform-agnostic interface for ANR (Application Not Responding) detection.
- *
- * ANRs occur when the main thread is blocked for too long (typically >5 seconds on Android).
- * Unlike crashes, ANRs don't throw exceptions - they're detected by monitoring thread responsiveness.
- */
-interface IOtelAnrDetector {
- /**
- * Starts monitoring for ANRs.
- * This should be called early in the app lifecycle, ideally right after crash handler initialization.
- */
- fun start()
-
- /**
- * Stops monitoring for ANRs.
- * Should be called when the app is shutting down or when monitoring is no longer needed.
- */
- fun stop()
-}
diff --git a/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/crash/OtelCrashHandler.kt b/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/crash/OtelCrashHandler.kt
deleted file mode 100644
index 9581c069ea..0000000000
--- a/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/crash/OtelCrashHandler.kt
+++ /dev/null
@@ -1,127 +0,0 @@
-package com.onesignal.otel.crash
-
-import com.onesignal.otel.IOtelCrashReporter
-import com.onesignal.otel.IOtelLogger
-import kotlinx.coroutines.runBlocking
-
-/**
- * Purpose: Writes any crashes involving OneSignal to a file where they can
- * later be send to OneSignal to help improve reliability.
- * NOTE: For future refactors, code is written assuming this is a singleton
- *
- * This should be initialized as early as possible, before any other initialization
- * that might crash. All fields must be pre-populated before initialization.
- */
-internal class OtelCrashHandler(
- private val crashReporter: IOtelCrashReporter,
- private val logger: IOtelLogger,
-) : Thread.UncaughtExceptionHandler, com.onesignal.otel.IOtelCrashHandler {
- private var existingHandler: Thread.UncaughtExceptionHandler? = null
- private val seenThrowables: MutableList = mutableListOf()
-
- @Volatile
- private var initialized = false
-
- override fun initialize() {
- if (initialized) {
- logger.warn("OtelCrashHandler already initialized, skipping")
- return
- }
- logger.info("OtelCrashHandler: Setting up uncaught exception handler...")
- existingHandler = Thread.getDefaultUncaughtExceptionHandler()
- Thread.setDefaultUncaughtExceptionHandler(this)
- initialized = true
- logger.info("OtelCrashHandler: ✅ Successfully initialized and registered as default uncaught exception handler")
- }
-
- override fun unregister() {
- if (!initialized) {
- logger.debug("OtelCrashHandler: Not initialized, nothing to unregister")
- return
- }
- logger.info("OtelCrashHandler: Unregistering — restoring previous exception handler")
- Thread.setDefaultUncaughtExceptionHandler(existingHandler)
- existingHandler = null
- initialized = false
- }
-
- override fun uncaughtException(thread: Thread, throwable: Throwable) {
- // Ensure we never attempt to process the same throwable instance
- // more than once. This would only happen if there was another crash
- // handler and was faulty in a specific way.
- synchronized(seenThrowables) {
- if (seenThrowables.contains(throwable)) {
- logger.warn("OtelCrashHandler: Ignoring duplicate throwable instance")
- return
- }
- seenThrowables.add(throwable)
- }
-
- logger.info("OtelCrashHandler: Uncaught exception detected - ${throwable.javaClass.simpleName}: ${throwable.message}")
-
- // Check if this is an ANR exception (though standalone ANR detector already handles ANRs)
- // This would only catch ANRs if they're thrown as exceptions, which is rare
- val isAnr = throwable.javaClass.simpleName.contains("ApplicationNotResponding", ignoreCase = true) ||
- throwable.message?.contains("Application Not Responding", ignoreCase = true) == true
-
- // NOTE: Future improvements:
- // - Catch anything we may throw and print only to logcat
- // - Send a stop command to OneSignalCrashUploader, give a bit of time to finish
- // and then call existingHandler. This way the app doesn't have to open a 2nd
- // time to get the crash report and should help prevent duplicated reports.
- // NOTE: ANRs are typically detected by the standalone OtelAnrDetector, which only
- // reports OneSignal-related ANRs. This handler would only catch ANRs if they're
- // thrown as exceptions (unlikely), and we still check if OneSignal is at fault.
- if (!isAnr && !isOneSignalAtFault(throwable)) {
- logger.debug("OtelCrashHandler: Crash is not OneSignal-related, delegating to existing handler")
- existingHandler?.uncaughtException(thread, throwable)
- return
- }
-
- if (isAnr) {
- logger.info("OtelCrashHandler: ANR exception caught (unusual - ANRs are usually detected by standalone detector)")
- }
-
- logger.info("OtelCrashHandler: OneSignal-related crash detected, saving crash report...")
-
- /**
- * NOTE: The order and running sequentially is important as:
- * The existingHandler.uncaughtException can immediately terminate the
- * process, either directly (if this is Android's
- * KillApplicationHandler) OR the app's handler / 3rd party SDK (either
- * directly or more likely, by it calling Android's
- * KillApplicationHandler).
- * Given this, we can't parallelize the existingHandler work with ours.
- * The safest thing is to try to finish our work as fast as possible
- * (including ensuring our logging write buffers are flushed) then call
- * the existingHandler so any crash handlers the app also has gets the
- * crash even too.
- *
- * NOTE: addShutdownHook() isn't a workaround as it doesn't fire for
- * Process.killProcess, which KillApplicationHandler calls.
- */
- try {
- runBlocking { crashReporter.saveCrash(thread, throwable) }
- logger.info("OtelCrashHandler: Crash report saved successfully")
- } catch (t: Throwable) {
- logger.error("OtelCrashHandler: Failed to save crash report: ${t.message} - ${t.javaClass.simpleName}")
- }
- logger.info("OtelCrashHandler: Delegating to existing crash handler")
- existingHandler?.uncaughtException(thread, throwable)
- }
-}
-
-/**
- * Checks if a throwable's stack trace indicates OneSignal is at fault.
- * Centralized logic used by both crash handler and ANR detector.
- */
-internal fun isOneSignalAtFault(throwable: Throwable): Boolean =
- isOneSignalAtFault(throwable.stackTrace)
-
-/**
- * Helper function to check if a stack trace indicates OneSignal is at fault.
- * Centralized logic used by both crash handler and ANR detector.
- * Made public so it can be accessed from core module.
- */
-fun isOneSignalAtFault(stackTrace: Array): Boolean =
- stackTrace.any { it.className.startsWith("com.onesignal") }
diff --git a/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/crash/OtelCrashReporter.kt b/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/crash/OtelCrashReporter.kt
deleted file mode 100644
index b875f906cb..0000000000
--- a/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/crash/OtelCrashReporter.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-package com.onesignal.otel.crash
-
-import com.onesignal.otel.IOtelLogger
-import com.onesignal.otel.IOtelOpenTelemetryCrash
-import io.opentelemetry.api.common.Attributes
-import io.opentelemetry.api.logs.Severity
-import java.time.Instant
-
-internal class OtelCrashReporter(
- private val openTelemetry: IOtelOpenTelemetryCrash,
- private val logger: IOtelLogger,
-) : com.onesignal.otel.IOtelCrashReporter {
- companion object {
- private const val OTEL_EXCEPTION_TYPE = "exception.type"
- private const val OTEL_EXCEPTION_MESSAGE = "exception.message"
- private const val OTEL_EXCEPTION_STACKTRACE = "exception.stacktrace"
- private const val OTEL_EXCEPTION_THREAD_NAME = "ossdk.exception.thread.name"
- }
-
- override suspend fun saveCrash(thread: Thread, throwable: Throwable) {
- try {
- logger.info("OtelCrashReporter: Starting to save crash report for ${throwable.javaClass.simpleName}")
-
- val attributes =
- Attributes
- .builder()
- .put(OTEL_EXCEPTION_MESSAGE, throwable.message ?: "")
- .put(OTEL_EXCEPTION_STACKTRACE, throwable.stackTraceToString())
- .put(OTEL_EXCEPTION_TYPE, throwable.javaClass.name)
- // This matches the top level thread.name today, but it may not
- // always if things are refactored to use a different thread.
- .put(OTEL_EXCEPTION_THREAD_NAME, thread.name)
- .build()
-
- logger.debug("OtelCrashReporter: Creating log record with attributes...")
- openTelemetry
- .getLogger()
- .setAllAttributes(attributes)
- .setSeverity(Severity.FATAL)
- .setTimestamp(Instant.now())
- .emit()
-
- logger.debug("OtelCrashReporter: Flushing crash report to disk...")
- openTelemetry.forceFlush()
-
- // Note: forceFlush() returns CompletableResultCode which is async
- // We wait for it in the implementation, so if we get here, it succeeded
- logger.info("OtelCrashReporter: ✅ Crash report saved and flushed successfully to disk")
- } catch (e: RuntimeException) {
- // If we fail to log the crash, at least try to log the failure
- logger.error("OtelCrashReporter: Failed to save crash report: ${e.message} - ${e.javaClass.simpleName}")
- throw e // Re-throw so caller knows it failed
- } catch (e: java.io.IOException) {
- // Handle IO errors specifically
- logger.error("OtelCrashReporter: IO error saving crash report: ${e.message}")
- throw e
- } catch (e: IllegalStateException) {
- // Handle illegal state errors
- logger.error("OtelCrashReporter: Illegal state error saving crash report: ${e.message}")
- throw e
- }
- }
-}
diff --git a/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/crash/OtelCrashUploader.kt b/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/crash/OtelCrashUploader.kt
deleted file mode 100644
index d9091b8a32..0000000000
--- a/OneSignalSDK/onesignal/otel/src/main/java/com/onesignal/otel/crash/OtelCrashUploader.kt
+++ /dev/null
@@ -1,91 +0,0 @@
-package com.onesignal.otel.crash
-
-import com.onesignal.otel.IOtelLogger
-import com.onesignal.otel.IOtelOpenTelemetryRemote
-import com.onesignal.otel.IOtelPlatformProvider
-import com.onesignal.otel.config.OtelConfigCrashFile
-import io.opentelemetry.sdk.logs.data.LogRecordData
-import kotlinx.coroutines.delay
-import java.util.concurrent.TimeUnit
-
-/**
- * Purpose: This reads a local crash report files created by OneSignal's
- * crash handler and sends them to OneSignal on the app's next start.
- *
- * This is fully platform-agnostic and can be used in KMP projects.
- * All platform-specific values are injected through IOtelPlatformProvider.
- *
- * Dependencies (all platform-agnostic):
- * - IOtelOpenTelemetryRemote: For network export (created via OtelFactory)
- * - IOtelPlatformProvider: Injects all platform values (Android/iOS)
- * - IOtelLogger: Platform logging interface (Android/iOS)
- *
- * Usage:
- * ```kotlin
- * val uploader = OtelFactory.createCrashUploader(platformProvider, logger)
- * coroutineScope.launch {
- * uploader.start()
- * }
- * ```
- */
-class OtelCrashUploader(
- private val openTelemetryRemote: IOtelOpenTelemetryRemote,
- private val platformProvider: IOtelPlatformProvider,
- private val logger: IOtelLogger,
-) {
- companion object {
- const val SEND_TIMEOUT_SECONDS = 30L
- }
-
- private fun getReports() =
- OtelConfigCrashFile.SdkLoggerProviderConfig
- .getFileLogRecordStorage(
- platformProvider.crashStoragePath,
- platformProvider.minFileAgeForReadMillis
- ).iterator()
-
- /**
- * Starts the crash uploader process.
- * This will periodically check for crash reports on disk and upload them to OneSignal.
- * If remote logging is disabled (NONE level), this function returns immediately without doing anything.
- */
- suspend fun start() {
- val remoteLogLevel = platformProvider.remoteLogLevel
- if (remoteLogLevel == null || remoteLogLevel == "NONE") {
- logger.info("OtelCrashUploader: remote logging disabled (level: $remoteLogLevel)")
- return
- }
-
- logger.info("OtelCrashUploader: starting")
- internalStart()
- }
-
- /**
- * NOTE: sendCrashReports is called twice for the these reasons:
- * 1. We want to send crash reports as soon as possible.
- * - App may crash quickly after starting a 2nd time.
- * 2. Reports could be delayed until the 2nd start after a crash
- * - Otel doesn't let you read a file it could be writing so we must
- * wait a minium amount of time after a crash to ensure we get the
- * report from the last crash.
- */
- suspend fun internalStart() {
- sendCrashReports(getReports())
- delay(platformProvider.minFileAgeForReadMillis)
- sendCrashReports(getReports())
- }
-
- private fun sendCrashReports(reports: Iterator>) {
- val networkExporter = openTelemetryRemote.logExporter
- var failed = false
- // NOTE: next() will delete the previous report, so we only want to send
- // another one if there isn't an issue making network calls.
- while (reports.hasNext() && !failed) {
- val future = networkExporter.export(reports.next())
- logger.debug("Sending OneSignal crash report")
- val result = future.join(SEND_TIMEOUT_SECONDS, TimeUnit.SECONDS)
- failed = !result.isSuccess
- logger.debug("Done OneSignal crash report, failed: $failed")
- }
- }
-}
diff --git a/OneSignalSDK/onesignal/otel/src/test/java/com/onesignal/otel/OneSignalOpenTelemetryTest.kt b/OneSignalSDK/onesignal/otel/src/test/java/com/onesignal/otel/OneSignalOpenTelemetryTest.kt
deleted file mode 100644
index 5bc57abf30..0000000000
--- a/OneSignalSDK/onesignal/otel/src/test/java/com/onesignal/otel/OneSignalOpenTelemetryTest.kt
+++ /dev/null
@@ -1,181 +0,0 @@
-package com.onesignal.otel
-
-import io.kotest.core.spec.style.FunSpec
-import io.kotest.matchers.shouldNotBe
-import io.kotest.matchers.types.shouldBeInstanceOf
-import io.mockk.clearMocks
-import io.mockk.coEvery
-import io.mockk.every
-import io.mockk.mockk
-import io.opentelemetry.api.common.Attributes
-import io.opentelemetry.api.logs.LogRecordBuilder
-import kotlinx.coroutines.runBlocking
-
-class OneSignalOpenTelemetryTest : FunSpec({
- val mockPlatformProvider = mockk(relaxed = true)
-
- fun setupDefaultMocks() {
- coEvery { mockPlatformProvider.getInstallId() } returns "test-install-id"
- every { mockPlatformProvider.sdkBase } returns "android"
- every { mockPlatformProvider.sdkBaseVersion } returns "5.0.0"
- every { mockPlatformProvider.appPackageId } returns "com.test.app"
- every { mockPlatformProvider.appVersion } returns "1.0.0"
- every { mockPlatformProvider.deviceManufacturer } returns "TestManufacturer"
- every { mockPlatformProvider.deviceModel } returns "TestModel"
- every { mockPlatformProvider.osName } returns "Android"
- every { mockPlatformProvider.osVersion } returns "13"
- every { mockPlatformProvider.osBuildId } returns "TEST123"
- every { mockPlatformProvider.sdkWrapper } returns null
- every { mockPlatformProvider.sdkWrapperVersion } returns null
- every { mockPlatformProvider.appId } returns "test-app-id"
- every { mockPlatformProvider.appIdForHeaders } returns "test-app-id"
- every { mockPlatformProvider.onesignalId } returns "test-onesignal-id"
- every { mockPlatformProvider.pushSubscriptionId } returns "test-subscription-id"
- every { mockPlatformProvider.appState } returns "foreground"
- every { mockPlatformProvider.processUptime } returns 100L
- every { mockPlatformProvider.currentThreadName } returns "main"
- every { mockPlatformProvider.crashStoragePath } returns "/test/path"
- every { mockPlatformProvider.minFileAgeForReadMillis } returns 5000L
- every { mockPlatformProvider.remoteLogLevel } returns "ERROR"
- every { mockPlatformProvider.apiBaseUrl } returns "https://api.onesignal.com"
- }
-
- beforeEach {
- clearMocks(mockPlatformProvider)
- setupDefaultMocks()
- }
-
- // ===== Remote Telemetry Tests =====
-
- test("createRemoteTelemetry should return IOtelOpenTelemetryRemote") {
- val remoteTelemetry = OtelFactory.createRemoteTelemetry(mockPlatformProvider)
-
- remoteTelemetry.shouldBeInstanceOf()
- }
-
- test("remote telemetry should have logExporter") {
- val remoteTelemetry = OtelFactory.createRemoteTelemetry(mockPlatformProvider)
-
- remoteTelemetry.logExporter shouldNotBe null
- }
-
- test("remote telemetry getLogger should return LogRecordBuilder") {
- val remoteTelemetry = OtelFactory.createRemoteTelemetry(mockPlatformProvider)
-
- runBlocking {
- val logger = remoteTelemetry.getLogger()
- logger.shouldBeInstanceOf()
- }
- }
-
- test("remote telemetry forceFlush should not throw") {
- val remoteTelemetry = OtelFactory.createRemoteTelemetry(mockPlatformProvider)
-
- runBlocking {
- // Should not throw
- remoteTelemetry.forceFlush()
- }
- }
-
- // ===== Crash Local Telemetry Tests =====
-
- test("createCrashLocalTelemetry should return IOtelOpenTelemetryCrash") {
- // Use temp directory for crash storage
- val tempDir = System.getProperty("java.io.tmpdir") + "/otel-test-" + System.currentTimeMillis()
- java.io.File(tempDir).mkdirs()
- every { mockPlatformProvider.crashStoragePath } returns tempDir
-
- try {
- val crashTelemetry = OtelFactory.createCrashLocalTelemetry(mockPlatformProvider)
-
- crashTelemetry.shouldBeInstanceOf()
- } finally {
- java.io.File(tempDir).deleteRecursively()
- }
- }
-
- test("crash telemetry getLogger should return LogRecordBuilder") {
- val tempDir = System.getProperty("java.io.tmpdir") + "/otel-test-" + System.currentTimeMillis()
- java.io.File(tempDir).mkdirs()
- every { mockPlatformProvider.crashStoragePath } returns tempDir
-
- try {
- val crashTelemetry = OtelFactory.createCrashLocalTelemetry(mockPlatformProvider)
-
- runBlocking {
- val logger = crashTelemetry.getLogger()
- logger.shouldBeInstanceOf()
- }
- } finally {
- java.io.File(tempDir).deleteRecursively()
- }
- }
-
- // ===== LogRecordBuilder Extension Tests =====
-
- test("setAllAttributes with Map should set all string attributes") {
- val mockBuilder = mockk(relaxed = true)
- val attributes = mapOf(
- "key1" to "value1",
- "key2" to "value2"
- )
-
- mockBuilder.setAllAttributes(attributes)
-
- io.mockk.verify { mockBuilder.setAttribute("key1", "value1") }
- io.mockk.verify { mockBuilder.setAttribute("key2", "value2") }
- }
-
- test("setAllAttributes with Attributes should handle different types") {
- val mockBuilder = mockk(relaxed = true)
- val attributes = Attributes.builder()
- .put("string.key", "string-value")
- .put("long.key", 123L)
- .put("double.key", 45.67)
- .put("boolean.key", true)
- .build()
-
- mockBuilder.setAllAttributes(attributes)
-
- io.mockk.verify { mockBuilder.setAttribute("string.key", "string-value") }
- io.mockk.verify { mockBuilder.setAttribute("long.key", 123L) }
- io.mockk.verify { mockBuilder.setAttribute("double.key", 45.67) }
- io.mockk.verify { mockBuilder.setAttribute("boolean.key", true) }
- }
-
- // ===== SDK Caching Tests =====
-
- test("remote telemetry should cache SDK instance") {
- val remoteTelemetry = OtelFactory.createRemoteTelemetry(mockPlatformProvider)
-
- runBlocking {
- val logger1 = remoteTelemetry.getLogger()
- val logger2 = remoteTelemetry.getLogger()
-
- // Both calls should succeed (SDK is cached internally)
- logger1 shouldNotBe null
- logger2 shouldNotBe null
- }
- }
-
- // ===== Integration with Factory Tests =====
-
- test("factory should create independent instances") {
- val remote1 = OtelFactory.createRemoteTelemetry(mockPlatformProvider)
- val remote2 = OtelFactory.createRemoteTelemetry(mockPlatformProvider)
-
- remote1 shouldNotBe remote2
- }
-
- test("factory should work with null optional fields") {
- every { mockPlatformProvider.appId } returns null
- every { mockPlatformProvider.onesignalId } returns null
- every { mockPlatformProvider.pushSubscriptionId } returns null
- every { mockPlatformProvider.sdkWrapper } returns null
- every { mockPlatformProvider.sdkWrapperVersion } returns null
-
- // Should not throw
- val remoteTelemetry = OtelFactory.createRemoteTelemetry(mockPlatformProvider)
- remoteTelemetry shouldNotBe null
- }
-})
diff --git a/OneSignalSDK/onesignal/otel/src/test/java/com/onesignal/otel/OtelFactoryTest.kt b/OneSignalSDK/onesignal/otel/src/test/java/com/onesignal/otel/OtelFactoryTest.kt
deleted file mode 100644
index 56f2ce5cc4..0000000000
--- a/OneSignalSDK/onesignal/otel/src/test/java/com/onesignal/otel/OtelFactoryTest.kt
+++ /dev/null
@@ -1,204 +0,0 @@
-package com.onesignal.otel
-
-import com.onesignal.otel.crash.OtelCrashUploader
-import io.kotest.core.spec.style.FunSpec
-import io.kotest.matchers.shouldNotBe
-import io.kotest.matchers.types.shouldBeInstanceOf
-import io.mockk.coEvery
-import io.mockk.every
-import io.mockk.mockk
-
-class OtelFactoryTest : FunSpec({
- val mockPlatformProvider = mockk(relaxed = true)
- val mockLogger = mockk(relaxed = true)
-
- beforeEach {
- // Setup default values
- every { mockPlatformProvider.sdkBase } returns "android"
- every { mockPlatformProvider.sdkBaseVersion } returns "1.0.0"
- every { mockPlatformProvider.appPackageId } returns "com.test.app"
- every { mockPlatformProvider.appVersion } returns "1.0"
- every { mockPlatformProvider.deviceManufacturer } returns "Test"
- every { mockPlatformProvider.deviceModel } returns "TestDevice"
- every { mockPlatformProvider.osName } returns "Android"
- every { mockPlatformProvider.osVersion } returns "10"
- every { mockPlatformProvider.osBuildId } returns "TEST123"
- every { mockPlatformProvider.sdkWrapper } returns null
- every { mockPlatformProvider.sdkWrapperVersion } returns null
- every { mockPlatformProvider.appId } returns null
- every { mockPlatformProvider.onesignalId } returns null
- every { mockPlatformProvider.pushSubscriptionId } returns null
- every { mockPlatformProvider.appState } returns "foreground"
- every { mockPlatformProvider.processUptime } returns 100L
- every { mockPlatformProvider.currentThreadName } returns "main"
- every { mockPlatformProvider.crashStoragePath } returns "/test/path"
- every { mockPlatformProvider.minFileAgeForReadMillis } returns 5000L
- every { mockPlatformProvider.remoteLogLevel } returns "ERROR"
- every { mockPlatformProvider.appIdForHeaders } returns "test-app-id"
- every { mockPlatformProvider.apiBaseUrl } returns "https://api.onesignal.com"
- coEvery { mockPlatformProvider.getInstallId() } returns "test-install-id"
- }
-
- // ===== createCrashHandler Tests =====
-
- test("createCrashHandler should return IOtelCrashHandler") {
- // When
- val handler = OtelFactory.createCrashHandler(mockPlatformProvider, mockLogger)
-
- // Then
- handler.shouldBeInstanceOf()
- }
-
- test("createCrashHandler should create handler with correct dependencies") {
- // When
- val handler = OtelFactory.createCrashHandler(mockPlatformProvider, mockLogger)
-
- // Then
- handler shouldNotBe null
- // Handler should be initializable
- handler.initialize()
- }
-
- test("createCrashHandler should create handler that can be initialized multiple times") {
- // Given
- val handler = OtelFactory.createCrashHandler(mockPlatformProvider, mockLogger)
-
- // When
- handler.initialize()
- handler.initialize() // Should not throw
-
- // Then - no exception thrown
- }
-
- // ===== createCrashUploader Tests =====
-
- test("createCrashUploader should return OtelCrashUploader") {
- // When
- val uploader = OtelFactory.createCrashUploader(mockPlatformProvider, mockLogger)
-
- // Then
- uploader shouldNotBe null
- uploader.shouldBeInstanceOf()
- }
-
- test("createCrashUploader should create uploader with correct dependencies") {
- // When
- val uploader = OtelFactory.createCrashUploader(mockPlatformProvider, mockLogger)
-
- // Then
- uploader shouldNotBe null
- }
-
- // ===== createRemoteTelemetry Tests =====
-
- test("createRemoteTelemetry should return IOtelOpenTelemetryRemote") {
- // When
- val telemetry = OtelFactory.createRemoteTelemetry(mockPlatformProvider)
-
- // Then
- telemetry shouldNotBe null
- telemetry.shouldBeInstanceOf()
- }
-
- test("createRemoteTelemetry should have logExporter") {
- // When
- val telemetry = OtelFactory.createRemoteTelemetry(mockPlatformProvider)
-
- // Then
- telemetry.logExporter shouldNotBe null
- }
-
- // ===== createCrashLocalTelemetry Tests =====
-
- test("createCrashLocalTelemetry should return IOtelOpenTelemetryCrash") {
- // When
- val telemetry = OtelFactory.createCrashLocalTelemetry(mockPlatformProvider)
-
- // Then
- telemetry shouldNotBe null
- telemetry.shouldBeInstanceOf()
- }
-
- test("createCrashLocalTelemetry should be different instance from remote") {
- // When
- val localTelemetry = OtelFactory.createCrashLocalTelemetry(mockPlatformProvider)
- val remoteTelemetry = OtelFactory.createRemoteTelemetry(mockPlatformProvider)
-
- // Then
- localTelemetry shouldNotBe remoteTelemetry
- }
-
- // ===== createCrashReporter Tests =====
-
- test("createCrashReporter should return IOtelCrashReporter") {
- // Given
- val crashTelemetry = OtelFactory.createCrashLocalTelemetry(mockPlatformProvider)
-
- // When
- val reporter = OtelFactory.createCrashReporter(crashTelemetry, mockLogger)
-
- // Then
- reporter shouldNotBe null
- reporter.shouldBeInstanceOf()
- }
-
- test("createCrashReporter should work with different telemetry instances") {
- // Given
- val crashTelemetry1 = OtelFactory.createCrashLocalTelemetry(mockPlatformProvider)
- val crashTelemetry2 = OtelFactory.createCrashLocalTelemetry(mockPlatformProvider)
-
- // When
- val reporter1 = OtelFactory.createCrashReporter(crashTelemetry1, mockLogger)
- val reporter2 = OtelFactory.createCrashReporter(crashTelemetry2, mockLogger)
-
- // Then
- reporter1 shouldNotBe null
- reporter2 shouldNotBe null
- reporter1 shouldNotBe reporter2
- }
-
- // ===== Integration Tests =====
-
- test("createCrashHandler uses platform provider values correctly") {
- // Given
- every { mockPlatformProvider.appId } returns "test-app-id"
- every { mockPlatformProvider.onesignalId } returns "test-onesignal-id"
-
- // When
- val handler = OtelFactory.createCrashHandler(mockPlatformProvider, mockLogger)
-
- // Then
- handler shouldNotBe null
- handler.initialize() // Should work with provided values
- }
-
- test("createCrashUploader uses platform provider values correctly") {
- // Given
- every { mockPlatformProvider.appId } returns "test-app-id"
- every { mockPlatformProvider.crashStoragePath } returns "/custom/path"
-
- // When
- val uploader = OtelFactory.createCrashUploader(mockPlatformProvider, mockLogger)
-
- // Then
- uploader shouldNotBe null
- }
-
- test("all factory methods work with null appId") {
- // Given
- every { mockPlatformProvider.appId } returns null
-
- // When & Then - should not throw
- val handler = OtelFactory.createCrashHandler(mockPlatformProvider, mockLogger)
- handler shouldNotBe null
-
- val uploader = OtelFactory.createCrashUploader(mockPlatformProvider, mockLogger)
- uploader shouldNotBe null
-
- val remoteTelemetry = OtelFactory.createRemoteTelemetry(mockPlatformProvider)
- remoteTelemetry shouldNotBe null
-
- val localTelemetry = OtelFactory.createCrashLocalTelemetry(mockPlatformProvider)
- localTelemetry shouldNotBe null
- }
-})
diff --git a/OneSignalSDK/onesignal/otel/src/test/java/com/onesignal/otel/OtelLoggingHelperTest.kt b/OneSignalSDK/onesignal/otel/src/test/java/com/onesignal/otel/OtelLoggingHelperTest.kt
deleted file mode 100644
index 16b195754e..0000000000
--- a/OneSignalSDK/onesignal/otel/src/test/java/com/onesignal/otel/OtelLoggingHelperTest.kt
+++ /dev/null
@@ -1,145 +0,0 @@
-package com.onesignal.otel
-
-import io.kotest.core.spec.style.FunSpec
-import io.kotest.matchers.shouldBe
-import io.mockk.coEvery
-import io.mockk.coVerify
-import io.mockk.every
-import io.mockk.mockk
-import io.mockk.slot
-import io.mockk.verify
-import io.opentelemetry.api.logs.LogRecordBuilder
-import io.opentelemetry.api.logs.Severity
-import kotlinx.coroutines.runBlocking
-
-class OtelLoggingHelperTest : FunSpec({
- val mockTelemetry = mockk(relaxed = true)
- val mockLogRecordBuilder = mockk(relaxed = true)
-
- beforeEach {
- coEvery { mockTelemetry.getLogger() } returns mockLogRecordBuilder
- }
-
- test("logToOtel should set correct severity for VERBOSE level") {
- val severitySlot = slot()
- every { mockLogRecordBuilder.setSeverity(capture(severitySlot)) } returns mockLogRecordBuilder
-
- runBlocking {
- OtelLoggingHelper.logToOtel(mockTelemetry, "VERBOSE", "test message")
- }
-
- severitySlot.captured shouldBe Severity.TRACE
- }
-
- test("logToOtel should set correct severity for DEBUG level") {
- val severitySlot = slot()
- every { mockLogRecordBuilder.setSeverity(capture(severitySlot)) } returns mockLogRecordBuilder
-
- runBlocking {
- OtelLoggingHelper.logToOtel(mockTelemetry, "DEBUG", "test message")
- }
-
- severitySlot.captured shouldBe Severity.DEBUG
- }
-
- test("logToOtel should set correct severity for INFO level") {
- val severitySlot = slot()
- every { mockLogRecordBuilder.setSeverity(capture(severitySlot)) } returns mockLogRecordBuilder
-
- runBlocking {
- OtelLoggingHelper.logToOtel(mockTelemetry, "INFO", "test message")
- }
-
- severitySlot.captured shouldBe Severity.INFO
- }
-
- test("logToOtel should set correct severity for WARN level") {
- val severitySlot = slot()
- every { mockLogRecordBuilder.setSeverity(capture(severitySlot)) } returns mockLogRecordBuilder
-
- runBlocking {
- OtelLoggingHelper.logToOtel(mockTelemetry, "WARN", "test message")
- }
-
- severitySlot.captured shouldBe Severity.WARN
- }
-
- test("logToOtel should set correct severity for ERROR level") {
- val severitySlot = slot()
- every { mockLogRecordBuilder.setSeverity(capture(severitySlot)) } returns mockLogRecordBuilder
-
- runBlocking {
- OtelLoggingHelper.logToOtel(mockTelemetry, "ERROR", "test message")
- }
-
- severitySlot.captured shouldBe Severity.ERROR
- }
-
- test("logToOtel should set correct severity for FATAL level") {
- val severitySlot = slot()
- every { mockLogRecordBuilder.setSeverity(capture(severitySlot)) } returns mockLogRecordBuilder
-
- runBlocking {
- OtelLoggingHelper.logToOtel(mockTelemetry, "FATAL", "test message")
- }
-
- severitySlot.captured shouldBe Severity.FATAL
- }
-
- test("logToOtel should default to INFO for unknown level") {
- val severitySlot = slot()
- every { mockLogRecordBuilder.setSeverity(capture(severitySlot)) } returns mockLogRecordBuilder
-
- runBlocking {
- OtelLoggingHelper.logToOtel(mockTelemetry, "UNKNOWN", "test message")
- }
-
- severitySlot.captured shouldBe Severity.INFO
- }
-
- test("logToOtel should set body with message") {
- val bodySlot = slot()
- every { mockLogRecordBuilder.setBody(capture(bodySlot)) } returns mockLogRecordBuilder
-
- runBlocking {
- OtelLoggingHelper.logToOtel(mockTelemetry, "INFO", "my test message")
- }
-
- bodySlot.captured shouldBe "my test message"
- }
-
- test("logToOtel should emit the log record") {
- runBlocking {
- OtelLoggingHelper.logToOtel(mockTelemetry, "INFO", "test message")
- }
-
- verify { mockLogRecordBuilder.emit() }
- }
-
- test("logToOtel should include exception attributes when provided") {
- runBlocking {
- OtelLoggingHelper.logToOtel(
- telemetry = mockTelemetry,
- level = "ERROR",
- message = "error occurred",
- exceptionType = "java.lang.RuntimeException",
- exceptionMessage = "something went wrong",
- exceptionStacktrace = "at com.test.Class.method(Class.kt:10)"
- )
- }
-
- coVerify { mockTelemetry.getLogger() }
- verify { mockLogRecordBuilder.emit() }
- }
-
- test("logToOtel should handle case-insensitive log levels") {
- val severitySlot = slot()
- every { mockLogRecordBuilder.setSeverity(capture(severitySlot)) } returns mockLogRecordBuilder
-
- runBlocking {
- OtelLoggingHelper.logToOtel(mockTelemetry, "error", "test message")
- }
-
- severitySlot.captured shouldBe Severity.ERROR
- }
-})
diff --git a/OneSignalSDK/onesignal/otel/src/test/java/com/onesignal/otel/attributes/OtelFieldsPerEventTest.kt b/OneSignalSDK/onesignal/otel/src/test/java/com/onesignal/otel/attributes/OtelFieldsPerEventTest.kt
deleted file mode 100644
index 6fec492830..0000000000
--- a/OneSignalSDK/onesignal/otel/src/test/java/com/onesignal/otel/attributes/OtelFieldsPerEventTest.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-package com.onesignal.otel.attributes
-
-import com.onesignal.otel.IOtelPlatformProvider
-import io.kotest.core.spec.style.FunSpec
-import io.kotest.matchers.collections.shouldContain
-import io.kotest.matchers.collections.shouldNotContain
-import io.kotest.matchers.shouldBe
-import io.kotest.matchers.shouldNotBe
-import io.mockk.clearMocks
-import io.mockk.every
-import io.mockk.mockk
-
-class OtelFieldsPerEventTest : FunSpec({
- val mockPlatformProvider = mockk(relaxed = true)
- val fields = OtelFieldsPerEvent(mockPlatformProvider)
-
- fun setupDefaultMocks(
- appId: String? = "test-app-id",
- onesignalId: String? = "test-onesignal-id",
- pushSubscriptionId: String? = "test-subscription-id",
- appState: String = "foreground",
- processUptime: Long = 100,
- threadName: String = "main-thread"
- ) {
- every { mockPlatformProvider.appId } returns appId
- every { mockPlatformProvider.onesignalId } returns onesignalId
- every { mockPlatformProvider.pushSubscriptionId } returns pushSubscriptionId
- every { mockPlatformProvider.appState } returns appState
- every { mockPlatformProvider.processUptime } returns processUptime
- every { mockPlatformProvider.currentThreadName } returns threadName
- }
-
- beforeEach { clearMocks(mockPlatformProvider) }
-
- test("getAttributes should include all per-event fields when all values present") {
- setupDefaultMocks()
-
- val attributes = fields.getAttributes()
-
- attributes.keys shouldContain "log.record.uid"
- attributes["log.record.uid"] shouldNotBe null
- attributes["ossdk.app_id"] shouldBe "test-app-id"
- attributes["ossdk.onesignal_id"] shouldBe "test-onesignal-id"
- attributes["ossdk.push_subscription_id"] shouldBe "test-subscription-id"
- attributes["app.state"] shouldBe "foreground"
- attributes["process.uptime"] shouldBe "100"
- attributes["thread.name"] shouldBe "main-thread"
- }
-
- test("getAttributes should exclude null optional fields") {
- setupDefaultMocks(appId = null, onesignalId = null, pushSubscriptionId = null, appState = "background")
-
- val attributes = fields.getAttributes()
-
- attributes.keys shouldNotContain "ossdk.app_id"
- attributes.keys shouldNotContain "ossdk.onesignal_id"
- attributes.keys shouldNotContain "ossdk.push_subscription_id"
- attributes["app.state"] shouldBe "background"
- }
-
- test("getAttributes should generate unique record IDs on each call") {
- setupDefaultMocks()
-
- val uid1 = fields.getAttributes()["log.record.uid"]
- val uid2 = fields.getAttributes()["log.record.uid"]
-
- uid1 shouldNotBe uid2
- }
-})
diff --git a/OneSignalSDK/onesignal/otel/src/test/java/com/onesignal/otel/attributes/OtelFieldsTopLevelTest.kt b/OneSignalSDK/onesignal/otel/src/test/java/com/onesignal/otel/attributes/OtelFieldsTopLevelTest.kt
deleted file mode 100644
index 6f6463475b..0000000000
--- a/OneSignalSDK/onesignal/otel/src/test/java/com/onesignal/otel/attributes/OtelFieldsTopLevelTest.kt
+++ /dev/null
@@ -1,78 +0,0 @@
-package com.onesignal.otel.attributes
-
-import com.onesignal.otel.IOtelPlatformProvider
-import io.kotest.core.spec.style.FunSpec
-import io.kotest.matchers.collections.shouldNotContain
-import io.kotest.matchers.shouldBe
-import io.mockk.clearMocks
-import io.mockk.coEvery
-import io.mockk.every
-import io.mockk.mockk
-import kotlinx.coroutines.runBlocking
-
-class OtelFieldsTopLevelTest : FunSpec({
- val mockPlatformProvider = mockk(relaxed = true)
- val fields = OtelFieldsTopLevel(mockPlatformProvider)
-
- fun setupDefaultMocks(
- installId: String = "test-install-id",
- sdkWrapper: String? = null,
- sdkWrapperVersion: String? = null
- ) {
- coEvery { mockPlatformProvider.getInstallId() } returns installId
- every { mockPlatformProvider.sdkBase } returns "android"
- every { mockPlatformProvider.sdkBaseVersion } returns "1.0.0"
- every { mockPlatformProvider.appPackageId } returns "com.test.app"
- every { mockPlatformProvider.appVersion } returns "1.0"
- every { mockPlatformProvider.deviceManufacturer } returns "TestManufacturer"
- every { mockPlatformProvider.deviceModel } returns "TestModel"
- every { mockPlatformProvider.osName } returns "Android"
- every { mockPlatformProvider.osVersion } returns "10"
- every { mockPlatformProvider.osBuildId } returns "TEST123"
- every { mockPlatformProvider.sdkWrapper } returns sdkWrapper
- every { mockPlatformProvider.sdkWrapperVersion } returns sdkWrapperVersion
- }
-
- beforeEach { clearMocks(mockPlatformProvider) }
-
- test("getAttributes should include all required top-level fields") {
- setupDefaultMocks()
-
- runBlocking {
- val attributes = fields.getAttributes()
-
- attributes["ossdk.install_id"] shouldBe "test-install-id"
- attributes["ossdk.sdk_base"] shouldBe "android"
- attributes["ossdk.sdk_base_version"] shouldBe "1.0.0"
- attributes["ossdk.app_package_id"] shouldBe "com.test.app"
- attributes["ossdk.app_version"] shouldBe "1.0"
- attributes["device.manufacturer"] shouldBe "TestManufacturer"
- attributes["device.model.identifier"] shouldBe "TestModel"
- attributes["os.name"] shouldBe "Android"
- attributes["os.version"] shouldBe "10"
- attributes["os.build_id"] shouldBe "TEST123"
- }
- }
-
- test("getAttributes should include wrapper fields when present") {
- setupDefaultMocks(sdkWrapper = "unity", sdkWrapperVersion = "2.0.0")
-
- runBlocking {
- val attributes = fields.getAttributes()
-
- attributes["ossdk.sdk_wrapper"] shouldBe "unity"
- attributes["ossdk.sdk_wrapper_version"] shouldBe "2.0.0"
- }
- }
-
- test("getAttributes should exclude null wrapper fields") {
- setupDefaultMocks(sdkWrapper = null, sdkWrapperVersion = null)
-
- runBlocking {
- val attributes = fields.getAttributes()
-
- attributes.keys shouldNotContain "ossdk.sdk_wrapper"
- attributes.keys shouldNotContain "ossdk.sdk_wrapper_version"
- }
- }
-})
diff --git a/OneSignalSDK/onesignal/otel/src/test/java/com/onesignal/otel/config/OtelConfigTest.kt b/OneSignalSDK/onesignal/otel/src/test/java/com/onesignal/otel/config/OtelConfigTest.kt
deleted file mode 100644
index f4f8daaf18..0000000000
--- a/OneSignalSDK/onesignal/otel/src/test/java/com/onesignal/otel/config/OtelConfigTest.kt
+++ /dev/null
@@ -1,136 +0,0 @@
-package com.onesignal.otel.config
-
-import io.kotest.core.spec.style.FunSpec
-import io.kotest.matchers.shouldBe
-import io.kotest.matchers.shouldNotBe
-import io.opentelemetry.semconv.ServiceAttributes
-
-class OtelConfigTest : FunSpec({
-
- // ===== OtelConfigShared.ResourceConfig Tests =====
-
- test("ResourceConfig should create resource with service name") {
- val resource = OtelConfigShared.ResourceConfig.create(emptyMap())
-
- resource.attributes.get(ServiceAttributes.SERVICE_NAME) shouldBe "OneSignalDeviceSDK"
- }
-
- test("ResourceConfig should include custom attributes") {
- val customAttributes = mapOf(
- "custom.key1" to "value1",
- "custom.key2" to "value2"
- )
-
- val resource = OtelConfigShared.ResourceConfig.create(customAttributes)
-
- resource.attributes.get(ServiceAttributes.SERVICE_NAME) shouldBe "OneSignalDeviceSDK"
- resource.attributes.asMap().entries.any { it.key.key == "custom.key1" } shouldBe true
- resource.attributes.asMap().entries.any { it.key.key == "custom.key2" } shouldBe true
- }
-
- test("ResourceConfig should handle empty attributes map") {
- val resource = OtelConfigShared.ResourceConfig.create(emptyMap())
-
- resource shouldNotBe null
- resource.attributes.get(ServiceAttributes.SERVICE_NAME) shouldBe "OneSignalDeviceSDK"
- }
-
- // ===== OtelConfigShared.LogLimitsConfig Tests =====
-
- test("LogLimitsConfig should create valid log limits") {
- val logLimits = OtelConfigShared.LogLimitsConfig.logLimits()
-
- logLimits shouldNotBe null
- logLimits.maxNumberOfAttributes shouldBe 128
- logLimits.maxAttributeValueLength shouldBe 32000
- }
-
- // ===== OtelConfigShared.LogRecordProcessorConfig Tests =====
-
- test("LogRecordProcessorConfig should create batch processor") {
- val mockExporter = io.mockk.mockk(relaxed = true)
-
- val processor = OtelConfigShared.LogRecordProcessorConfig.batchLogRecordProcessor(mockExporter)
-
- processor shouldNotBe null
- }
-
- // ===== OtelConfigRemoteOneSignal Tests =====
-
- test("buildEndpoint should construct correct URL from base and appId") {
- val endpoint = OtelConfigRemoteOneSignal.buildEndpoint("https://api.onesignal.com", "my-app")
- endpoint shouldBe "https://api.onesignal.com/sdk/otel/v1/logs?app_id=my-app"
- }
-
- test("HttpRecordBatchExporter should create exporter with correct endpoint") {
- val headers = mapOf("X-Test-Header" to "test-value")
- val appId = "test-app-id"
- val apiBaseUrl = "https://api.onesignal.com"
-
- val exporter = OtelConfigRemoteOneSignal.HttpRecordBatchExporter.create(headers, appId, apiBaseUrl)
-
- exporter shouldNotBe null
- }
-
- test("LogRecordExporterConfig should create OTLP HTTP exporter") {
- val headers = mapOf("Authorization" to "Bearer token")
- val endpoint = "https://example.com/v1/logs"
-
- val exporter = OtelConfigRemoteOneSignal.LogRecordExporterConfig.otlpHttpLogRecordExporter(
- headers,
- endpoint
- )
-
- exporter shouldNotBe null
- }
-
- test("SdkLoggerProviderConfig should create logger provider") {
- val resource = OtelConfigShared.ResourceConfig.create(emptyMap())
- val headers = mapOf("X-OneSignal-App-Id" to "test-app-id")
-
- val provider = OtelConfigRemoteOneSignal.SdkLoggerProviderConfig.create(
- resource,
- headers,
- "test-app-id",
- "https://api.onesignal.com",
- )
-
- provider shouldNotBe null
- }
-
- // ===== OtelConfigCrashFile Tests =====
-
- test("OtelConfigCrashFile should create file log storage") {
- val tempDir = System.getProperty("java.io.tmpdir") + "/otel-test-" + System.currentTimeMillis()
- java.io.File(tempDir).mkdirs()
-
- try {
- val storage = OtelConfigCrashFile.SdkLoggerProviderConfig.getFileLogRecordStorage(
- tempDir,
- 5000L
- )
-
- storage shouldNotBe null
- } finally {
- java.io.File(tempDir).deleteRecursively()
- }
- }
-
- test("OtelConfigCrashFile should create logger provider") {
- val resource = OtelConfigShared.ResourceConfig.create(emptyMap())
- val tempDir = System.getProperty("java.io.tmpdir") + "/otel-test-" + System.currentTimeMillis()
- java.io.File(tempDir).mkdirs()
-
- try {
- val provider = OtelConfigCrashFile.SdkLoggerProviderConfig.create(
- resource,
- tempDir,
- 5000L
- )
-
- provider shouldNotBe null
- } finally {
- java.io.File(tempDir).deleteRecursively()
- }
- }
-})
diff --git a/OneSignalSDK/onesignal/otel/src/test/java/com/onesignal/otel/crash/OtelCrashHandlerTest.kt b/OneSignalSDK/onesignal/otel/src/test/java/com/onesignal/otel/crash/OtelCrashHandlerTest.kt
deleted file mode 100644
index 2572c2f162..0000000000
--- a/OneSignalSDK/onesignal/otel/src/test/java/com/onesignal/otel/crash/OtelCrashHandlerTest.kt
+++ /dev/null
@@ -1,169 +0,0 @@
-package com.onesignal.otel.crash
-
-import com.onesignal.otel.IOtelCrashReporter
-import com.onesignal.otel.IOtelLogger
-import io.kotest.core.spec.style.FunSpec
-import io.kotest.matchers.shouldBe
-import io.mockk.clearMocks
-import io.mockk.coEvery
-import io.mockk.coVerify
-import io.mockk.mockk
-import io.mockk.verify
-
-class OtelCrashHandlerTest : FunSpec({
- val mockCrashReporter = mockk(relaxed = true)
- val mockLogger = mockk(relaxed = true)
-
- fun createFreshHandler() = OtelCrashHandler(mockCrashReporter, mockLogger)
-
- beforeEach {
- clearMocks(mockCrashReporter, mockLogger)
- }
-
- test("initialize should set up uncaught exception handler") {
- val originalHandler = Thread.getDefaultUncaughtExceptionHandler()
- val crashHandler = createFreshHandler()
-
- crashHandler.initialize()
-
- Thread.getDefaultUncaughtExceptionHandler() shouldBe crashHandler
- verify { mockLogger.info(match { it.contains("Setting up uncaught exception handler") }) }
- verify { mockLogger.info(match { it.contains("Successfully initialized") }) }
-
- Thread.setDefaultUncaughtExceptionHandler(originalHandler)
- }
-
- test("initialize should not initialize twice") {
- val originalHandler = Thread.getDefaultUncaughtExceptionHandler()
- val crashHandler = createFreshHandler()
-
- crashHandler.initialize()
- crashHandler.initialize()
-
- verify(exactly = 1) { mockLogger.warn("OtelCrashHandler already initialized, skipping") }
-
- Thread.setDefaultUncaughtExceptionHandler(originalHandler)
- }
-
- test("uncaughtException should not process non-OneSignal crashes") {
- val originalHandler = Thread.getDefaultUncaughtExceptionHandler()
- val mockHandler = mockk(relaxed = true)
- Thread.setDefaultUncaughtExceptionHandler(mockHandler)
- val crashHandler = createFreshHandler()
- crashHandler.initialize()
-
- val throwable = RuntimeException("Non-OneSignal crash")
- val thread = Thread.currentThread()
-
- crashHandler.uncaughtException(thread, throwable)
-
- coVerify(exactly = 0) { mockCrashReporter.saveCrash(any(), any()) }
- verify { mockHandler.uncaughtException(thread, throwable) }
-
- Thread.setDefaultUncaughtExceptionHandler(originalHandler)
- }
-
- test("uncaughtException should process OneSignal crashes") {
- val originalHandler = Thread.getDefaultUncaughtExceptionHandler()
- val mockHandler = mockk(relaxed = true)
- Thread.setDefaultUncaughtExceptionHandler(mockHandler)
- val crashHandler = createFreshHandler()
- crashHandler.initialize()
-
- val throwable = RuntimeException("OneSignal crash").apply {
- stackTrace = arrayOf(
- StackTraceElement("com.onesignal.SomeClass", "someMethod", "SomeClass.kt", 10)
- )
- }
- val thread = Thread.currentThread()
-
- coEvery { mockCrashReporter.saveCrash(any(), any()) } returns Unit
-
- crashHandler.uncaughtException(thread, throwable)
-
- coVerify(exactly = 1) { mockCrashReporter.saveCrash(thread, throwable) }
- verify { mockHandler.uncaughtException(thread, throwable) }
-
- Thread.setDefaultUncaughtExceptionHandler(originalHandler)
- }
-
- test("uncaughtException should not process same throwable twice") {
- val originalHandler = Thread.getDefaultUncaughtExceptionHandler()
- val crashHandler = createFreshHandler()
- crashHandler.initialize()
-
- val throwable = RuntimeException("OneSignal crash").apply {
- stackTrace = arrayOf(
- StackTraceElement("com.onesignal.SomeClass", "someMethod", "SomeClass.kt", 10)
- )
- }
- val thread = Thread.currentThread()
-
- coEvery { mockCrashReporter.saveCrash(any(), any()) } returns Unit
-
- crashHandler.uncaughtException(thread, throwable)
- crashHandler.uncaughtException(thread, throwable)
-
- coVerify(exactly = 1) { mockCrashReporter.saveCrash(any(), any()) }
-
- Thread.setDefaultUncaughtExceptionHandler(originalHandler)
- }
-
- test("uncaughtException should handle crash reporter failures gracefully") {
- val originalHandler = Thread.getDefaultUncaughtExceptionHandler()
- val mockHandler = mockk(relaxed = true)
- Thread.setDefaultUncaughtExceptionHandler(mockHandler)
- val crashHandler = createFreshHandler()
- crashHandler.initialize()
-
- val throwable = RuntimeException("OneSignal crash").apply {
- stackTrace = arrayOf(
- StackTraceElement("com.onesignal.SomeClass", "someMethod", "SomeClass.kt", 10)
- )
- }
- val thread = Thread.currentThread()
-
- coEvery { mockCrashReporter.saveCrash(any(), any()) } throws RuntimeException("Reporter failed")
-
- crashHandler.uncaughtException(thread, throwable)
-
- verify { mockLogger.error(match { it.contains("Failed to save crash report") }) }
- verify { mockHandler.uncaughtException(thread, throwable) }
-
- Thread.setDefaultUncaughtExceptionHandler(originalHandler)
- }
-
- // ===== isOneSignalAtFault Tests =====
-
- test("isOneSignalAtFault should return true for OneSignal stack traces") {
- val stackTrace = arrayOf(
- StackTraceElement("com.onesignal.core.SomeClass", "method", "File.kt", 10)
- )
-
- isOneSignalAtFault(stackTrace) shouldBe true
- }
-
- test("isOneSignalAtFault should return false for non-OneSignal stack traces") {
- val stackTrace = arrayOf(
- StackTraceElement("com.example.app.SomeClass", "method", "File.kt", 10)
- )
-
- isOneSignalAtFault(stackTrace) shouldBe false
- }
-
- test("isOneSignalAtFault should return false for empty stack traces") {
- val stackTrace = emptyArray()
-
- isOneSignalAtFault(stackTrace) shouldBe false
- }
-
- test("isOneSignalAtFault with throwable should check throwable stack trace") {
- val throwable = RuntimeException("test").apply {
- stackTrace = arrayOf(
- StackTraceElement("com.onesignal.SomeClass", "method", "File.kt", 10)
- )
- }
-
- isOneSignalAtFault(throwable) shouldBe true
- }
-})
diff --git a/OneSignalSDK/onesignal/otel/src/test/java/com/onesignal/otel/crash/OtelCrashReporterTest.kt b/OneSignalSDK/onesignal/otel/src/test/java/com/onesignal/otel/crash/OtelCrashReporterTest.kt
deleted file mode 100644
index 3b9e27c77c..0000000000
--- a/OneSignalSDK/onesignal/otel/src/test/java/com/onesignal/otel/crash/OtelCrashReporterTest.kt
+++ /dev/null
@@ -1,146 +0,0 @@
-package com.onesignal.otel.crash
-
-import com.onesignal.otel.IOtelCrashReporter
-import com.onesignal.otel.IOtelLogger
-import com.onesignal.otel.IOtelOpenTelemetryCrash
-import io.kotest.assertions.throwables.shouldThrow
-import io.kotest.core.spec.style.FunSpec
-import io.kotest.matchers.types.shouldBeInstanceOf
-import io.mockk.clearMocks
-import io.mockk.coEvery
-import io.mockk.coVerify
-import io.mockk.every
-import io.mockk.mockk
-import io.mockk.verify
-import io.opentelemetry.api.logs.LogRecordBuilder
-import io.opentelemetry.api.logs.Severity
-import io.opentelemetry.sdk.common.CompletableResultCode
-import kotlinx.coroutines.runBlocking
-
-class OtelCrashReporterTest : FunSpec({
- val mockOpenTelemetry = mockk(relaxed = true)
- val mockLogger = mockk(relaxed = true)
- val mockLogRecordBuilder = mockk(relaxed = true)
- val mockCompletableResult = mockk(relaxed = true)
-
- fun setupDefaultMocks() {
- coEvery { mockOpenTelemetry.getLogger() } returns mockLogRecordBuilder
- coEvery { mockOpenTelemetry.forceFlush() } returns mockCompletableResult
- every { mockLogRecordBuilder.setSeverity(any()) } returns mockLogRecordBuilder
- every { mockLogRecordBuilder.setTimestamp(any()) } returns mockLogRecordBuilder
- every { mockLogRecordBuilder.emit() } returns Unit
- }
-
- beforeEach {
- clearMocks(mockOpenTelemetry, mockLogger, mockLogRecordBuilder, mockCompletableResult)
- setupDefaultMocks()
- }
-
- test("should implement IOtelCrashReporter interface") {
- val crashReporter = OtelCrashReporter(mockOpenTelemetry, mockLogger)
-
- crashReporter.shouldBeInstanceOf()
- }
-
- test("saveCrash should get logger and emit log record") {
- val crashReporter = OtelCrashReporter(mockOpenTelemetry, mockLogger)
- val throwable = RuntimeException("Test crash")
- val thread = Thread.currentThread()
-
- runBlocking {
- crashReporter.saveCrash(thread, throwable)
- }
-
- coVerify(exactly = 1) { mockOpenTelemetry.getLogger() }
- coVerify(exactly = 1) { mockOpenTelemetry.forceFlush() }
- verify { mockLogRecordBuilder.setSeverity(Severity.FATAL) }
- verify { mockLogRecordBuilder.emit() }
- }
-
- test("saveCrash should log info messages") {
- val crashReporter = OtelCrashReporter(mockOpenTelemetry, mockLogger)
- val throwable = RuntimeException("Test crash")
- val thread = Thread.currentThread()
-
- runBlocking {
- crashReporter.saveCrash(thread, throwable)
- }
-
- verify { mockLogger.info(match { it.contains("Starting to save crash report") }) }
- verify { mockLogger.info(match { it.contains("Crash report saved and flushed successfully") }) }
- }
-
- test("saveCrash should handle null exception message") {
- val crashReporter = OtelCrashReporter(mockOpenTelemetry, mockLogger)
- val throwable = RuntimeException() // No message
- val thread = Thread.currentThread()
-
- runBlocking {
- crashReporter.saveCrash(thread, throwable)
- }
-
- coVerify { mockOpenTelemetry.getLogger() }
- verify { mockLogRecordBuilder.emit() }
- }
-
- test("saveCrash should re-throw RuntimeException on failure") {
- coEvery { mockOpenTelemetry.getLogger() } throws RuntimeException("OpenTelemetry failed")
-
- val crashReporter = OtelCrashReporter(mockOpenTelemetry, mockLogger)
- val throwable = RuntimeException("Test crash")
- val thread = Thread.currentThread()
-
- shouldThrow {
- runBlocking {
- crashReporter.saveCrash(thread, throwable)
- }
- }
-
- verify { mockLogger.error(match { it.contains("Failed to save crash report") }) }
- }
-
- test("saveCrash should re-throw IOException on IO failure") {
- coEvery { mockOpenTelemetry.getLogger() } throws java.io.IOException("IO failed")
-
- val crashReporter = OtelCrashReporter(mockOpenTelemetry, mockLogger)
- val throwable = RuntimeException("Test crash")
- val thread = Thread.currentThread()
-
- shouldThrow {
- runBlocking {
- crashReporter.saveCrash(thread, throwable)
- }
- }
-
- verify { mockLogger.error(match { it.contains("IO error saving crash report") }) }
- }
-
- test("saveCrash should re-throw IllegalStateException") {
- coEvery { mockOpenTelemetry.getLogger() } throws IllegalStateException("Illegal state")
-
- val crashReporter = OtelCrashReporter(mockOpenTelemetry, mockLogger)
- val throwable = RuntimeException("Test crash")
- val thread = Thread.currentThread()
-
- // Note: IllegalStateException extends RuntimeException, so it gets caught by the RuntimeException handler
- shouldThrow {
- runBlocking {
- crashReporter.saveCrash(thread, throwable)
- }
- }
-
- verify { mockLogger.error(match { it.contains("Failed to save crash report") }) }
- }
-
- test("saveCrash should set timestamp") {
- val crashReporter = OtelCrashReporter(mockOpenTelemetry, mockLogger)
- val throwable = RuntimeException("Test crash")
- val thread = Thread.currentThread()
-
- runBlocking {
- crashReporter.saveCrash(thread, throwable)
- }
-
- verify { mockLogRecordBuilder.setTimestamp(any()) }
- }
-})
diff --git a/OneSignalSDK/onesignal/otel/src/test/java/com/onesignal/otel/crash/OtelCrashUploaderTest.kt b/OneSignalSDK/onesignal/otel/src/test/java/com/onesignal/otel/crash/OtelCrashUploaderTest.kt
deleted file mode 100644
index 4f46ef30d9..0000000000
--- a/OneSignalSDK/onesignal/otel/src/test/java/com/onesignal/otel/crash/OtelCrashUploaderTest.kt
+++ /dev/null
@@ -1,89 +0,0 @@
-package com.onesignal.otel.crash
-
-import com.onesignal.otel.IOtelLogger
-import com.onesignal.otel.IOtelOpenTelemetryRemote
-import com.onesignal.otel.IOtelPlatformProvider
-import io.kotest.core.spec.style.FunSpec
-import io.kotest.matchers.shouldBe
-import io.kotest.matchers.shouldNotBe
-import io.mockk.clearMocks
-import io.mockk.every
-import io.mockk.mockk
-import io.mockk.verify
-import io.opentelemetry.sdk.common.CompletableResultCode
-import io.opentelemetry.sdk.logs.export.LogRecordExporter
-import kotlinx.coroutines.runBlocking
-import java.io.File
-
-class OtelCrashUploaderTest : FunSpec({
- val mockRemoteTelemetry = mockk(relaxed = true)
- val mockPlatformProvider = mockk(relaxed = true)
- val mockLogger = mockk(relaxed = true)
- val mockExporter = mockk(relaxed = true)
-
- // Use temp directory for tests that need file system access
- fun createTempDir(): String {
- val tempDir = File(System.getProperty("java.io.tmpdir"), "otel-test-${System.currentTimeMillis()}")
- tempDir.mkdirs()
- return tempDir.absolutePath
- }
-
- fun setupDefaultMocks(
- remoteLogLevel: String? = "ERROR",
- crashStoragePath: String? = null,
- minFileAgeForReadMillis: Long = 0L // Use 0 to avoid delays in tests
- ) {
- val path = crashStoragePath ?: createTempDir()
- every { mockPlatformProvider.remoteLogLevel } returns remoteLogLevel
- every { mockPlatformProvider.crashStoragePath } returns path
- every { mockPlatformProvider.minFileAgeForReadMillis } returns minFileAgeForReadMillis
- every { mockRemoteTelemetry.logExporter } returns mockExporter
- every { mockExporter.export(any()) } returns CompletableResultCode.ofSuccess()
- }
-
- beforeEach {
- clearMocks(mockRemoteTelemetry, mockPlatformProvider, mockLogger, mockExporter)
- }
-
- test("should create uploader with dependencies") {
- setupDefaultMocks()
-
- val uploader = OtelCrashUploader(mockRemoteTelemetry, mockPlatformProvider, mockLogger)
-
- uploader shouldNotBe null
- }
-
- test("start should return immediately when remote logging is disabled (null)") {
- setupDefaultMocks(remoteLogLevel = null)
-
- val uploader = OtelCrashUploader(mockRemoteTelemetry, mockPlatformProvider, mockLogger)
-
- runBlocking { uploader.start() }
-
- verify { mockLogger.info("OtelCrashUploader: remote logging disabled (level: null)") }
- }
-
- test("start should return immediately when remote logging is NONE") {
- setupDefaultMocks(remoteLogLevel = "NONE")
-
- val uploader = OtelCrashUploader(mockRemoteTelemetry, mockPlatformProvider, mockLogger)
-
- runBlocking { uploader.start() }
-
- verify { mockLogger.info("OtelCrashUploader: remote logging disabled (level: NONE)") }
- }
-
- test("start should proceed when remote logging is enabled") {
- setupDefaultMocks(remoteLogLevel = "ERROR")
-
- val uploader = OtelCrashUploader(mockRemoteTelemetry, mockPlatformProvider, mockLogger)
-
- runBlocking { uploader.start() }
-
- verify { mockLogger.info("OtelCrashUploader: starting") }
- }
-
- test("SEND_TIMEOUT_SECONDS should be 30 seconds") {
- OtelCrashUploader.SEND_TIMEOUT_SECONDS shouldBe 30L
- }
-})
diff --git a/OneSignalSDK/settings.gradle b/OneSignalSDK/settings.gradle
index 3cdfa40b21..76fb5755e6 100644
--- a/OneSignalSDK/settings.gradle
+++ b/OneSignalSDK/settings.gradle
@@ -30,4 +30,3 @@ include ':OneSignal:in-app-messages'
include ':OneSignal:location'
include ':OneSignal:notifications'
include ':OneSignal:testhelpers'
-include ':OneSignal:otel'
diff --git a/examples/demo/app/build.gradle.kts b/examples/demo/app/build.gradle.kts
index 9dbda6eca6..8aea101410 100644
--- a/examples/demo/app/build.gradle.kts
+++ b/examples/demo/app/build.gradle.kts
@@ -1,11 +1,8 @@
plugins {
id("com.android.application")
id("kotlin-android")
- id("org.jetbrains.kotlin.plugin.compose") version "2.2.0"
}
-val kotlinVersion: String by rootProject.extra
-
// Apply GMS or Huawei plugin based on build variant
// Check at configuration time, not when task graph is ready
val taskRequests = gradle.startParameter.taskRequests.toString().lowercase()
@@ -36,6 +33,10 @@ android {
compose = true
}
+ composeOptions {
+ kotlinCompilerExtensionVersion = "1.5.14"
+ }
+
flavorDimensions += "default"
productFlavors {
@@ -89,7 +90,7 @@ android {
dependencies {
// Kotlin
- implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion")
+ implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.24")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
// AndroidX
diff --git a/examples/demo/app/src/main/java/com/onesignal/sdktest/application/MainApplication.kt b/examples/demo/app/src/main/java/com/onesignal/sdktest/application/MainApplication.kt
index 7df8fec384..2cfe743f3b 100644
--- a/examples/demo/app/src/main/java/com/onesignal/sdktest/application/MainApplication.kt
+++ b/examples/demo/app/src/main/java/com/onesignal/sdktest/application/MainApplication.kt
@@ -72,9 +72,8 @@ class MainApplication : MultiDexApplication() {
OneSignal.consentGiven = SharedPreferenceUtil.getUserPrivacyConsent(this)
// Initialize OneSignal on main thread (required)
- // Crash handler + ANR detector are initialized early inside initWithContext
OneSignal.initWithContext(this, appId)
- LogManager.i(TAG, "OneSignal init completed (crash handler, ANR detector, and logging active)")
+ LogManager.i(TAG, "OneSignal init completed")
// Set up all OneSignal listeners
setupOneSignalListeners()
diff --git a/examples/demo/app/src/main/java/com/onesignal/sdktest/ui/secondary/SecondaryActivity.kt b/examples/demo/app/src/main/java/com/onesignal/sdktest/ui/secondary/SecondaryActivity.kt
index 0655dc843c..6c3ef7f6cf 100644
--- a/examples/demo/app/src/main/java/com/onesignal/sdktest/ui/secondary/SecondaryActivity.kt
+++ b/examples/demo/app/src/main/java/com/onesignal/sdktest/ui/secondary/SecondaryActivity.kt
@@ -3,11 +3,8 @@ package com.onesignal.sdktest.ui.secondary
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
@@ -22,14 +19,9 @@ import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.unit.dp
-import com.onesignal.sdktest.ui.components.DestructiveButton
import com.onesignal.sdktest.ui.theme.LightBackground
import com.onesignal.sdktest.ui.theme.OneSignalRed
import com.onesignal.sdktest.ui.theme.OneSignalTheme
-import java.text.SimpleDateFormat
-import java.util.Date
-import java.util.Locale
class SecondaryActivity : ComponentActivity() {
@@ -59,45 +51,19 @@ class SecondaryActivity : ComponentActivity() {
},
containerColor = LightBackground
) { paddingValues ->
- Column(
+ Box(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues),
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.Center
+ contentAlignment = Alignment.Center
) {
Text(
text = "Secondary Activity",
style = MaterialTheme.typography.headlineMedium
)
-
- Spacer(modifier = Modifier.height(32.dp))
-
- DestructiveButton(
- text = "CRASH",
- onClick = { triggerCrash() }
- )
-
- Spacer(modifier = Modifier.height(16.dp))
-
- DestructiveButton(
- text = "SIMULATE ANR (10s block)",
- onClick = { triggerAnr() }
- )
}
}
}
}
}
-
- private fun triggerCrash() {
- val timestamp = SimpleDateFormat("MMM dd, yyyy HH:mm:ss", Locale.getDefault())
- .format(Date())
- throw RuntimeException("Test crash from OneSignal Demo App - $timestamp")
- }
-
- @Suppress("MagicNumber")
- private fun triggerAnr() {
- Thread.sleep(10_000)
- }
}
diff --git a/examples/demo/build.gradle.kts b/examples/demo/build.gradle.kts
index b21f952b6f..0244a29bc4 100644
--- a/examples/demo/build.gradle.kts
+++ b/examples/demo/build.gradle.kts
@@ -1,9 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
-val kotlinVersion by extra("2.2.0")
-
buildscript {
- val kotlinVersion: String by extra
repositories {
google()
mavenCentral()
@@ -13,7 +10,7 @@ buildscript {
}
dependencies {
classpath("com.android.tools.build:gradle:8.8.2")
- classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
+ classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.24")
classpath("com.google.gms:google-services:4.3.10")
classpath("com.huawei.agconnect:agcp:1.9.1.304")
}