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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions OneSignalSDK/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ buildscript {
huaweiAgconnectVersion = '1.9.1.304'
huaweiHMSPushVersion = '6.3.0.304'
huaweiHMSLocationVersion = '4.0.0.300'
kotlinVersion = '1.9.25'
dokkaVersion = '1.9.10' // Dokka version compatible with Kotlin 1.9.25
kotlinVersion = '2.2.0'
dokkaVersion = '1.9.10'
coroutinesVersion = '1.7.3'
kotestVersion = '5.8.0'
ioMockVersion = '1.13.2'
Expand All @@ -25,6 +25,10 @@ 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()
Expand All @@ -45,11 +49,9 @@ buildscript {
]
}

buildscript {
repositories sharedRepos
dependencies {
classpath sharedDeps
}
repositories sharedRepos
dependencies {
classpath sharedDeps
}
}

Expand Down
64 changes: 24 additions & 40 deletions OneSignalSDK/detekt/detekt-baseline-core.xml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion OneSignalSDK/detekt/detekt-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ comments:
UndocumentedPublicFunction:
active: true
excludes: ['**/test/**', '**/androidTest/**', '**/testhelpers/**']

EndOfSentenceFormat:
active: false
endOfSentenceFormat: ([.?!][ \t\n\r\f<])|([.?!:]$)
Expand Down
2 changes: 2 additions & 0 deletions OneSignalSDK/onesignal/core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ dependencies {
}
}

// Otel module dependency
implementation(project(':OneSignal:otel'))
testImplementation(project(':OneSignal:testhelpers'))

testImplementation("io.kotest:kotest-runner-junit5:$kotestVersion")
Expand Down
7 changes: 6 additions & 1 deletion OneSignalSDK/onesignal/core/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<!-- Override otel module's minSdk requirement (26) since we have runtime checks -->
<!-- The otel module is only used on SDK 26+, so this is safe -->
<uses-sdk tools:overrideLibrary="com.onesignal.otel" />

<!-- Required so the device can access the internet. -->
<uses-permission android:name="android.permission.INTERNET" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ object JSONUtils {
try {
val value = jsonObject.opt(key)
if (value is JSONArray || value is JSONObject) {
Logging.error("Omitting key '$key'! sendTags DO NOT supported nested values!")
Logging.warn("Omitting key '$key'! sendTags DO NOT supported nested values!")
} else if (jsonObject.isNull(key) || "" == value) {
result[key] = ""
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ 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
Expand Down Expand Up @@ -81,6 +82,9 @@ internal class CoreModule : IModule {
// Purchase Tracking
builder.register<TrackGooglePurchase>().provides<IStartableService>()

// Crash Uploader (crash handler is initialized directly in OneSignalImp for early initialization)
builder.register<OneSignalCrashUploaderWrapper>().provides<IStartableService>()

// 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<MisconfiguredNotificationsManager>().provides<INotificationsManager>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.onesignal.core.internal.backend

import org.json.JSONArray

interface IParamsBackendService {
internal interface IParamsBackendService {
/**
* Retrieve the configuration parameters for the [appId] and optional [subscriptionId].
*
Expand All @@ -20,7 +20,8 @@ interface IParamsBackendService {
): ParamsObject
}

class ParamsObject(
@Suppress("LongParameterList")
internal class ParamsObject(
var googleProjectNumber: String? = null,
var enterprise: Boolean? = null,
var useIdentityVerification: Boolean? = null,
Expand All @@ -36,9 +37,10 @@ class ParamsObject(
var opRepoExecutionInterval: Long? = null,
var influenceParams: InfluenceParamsObject,
var fcmParams: FCMParamsObject,
val remoteLoggingParams: RemoteLoggingParamsObject,
)

class InfluenceParamsObject(
internal class InfluenceParamsObject(
val indirectNotificationAttributionWindow: Int? = null,
val notificationLimit: Int? = null,
val indirectIAMAttributionWindow: Int? = null,
Expand All @@ -48,8 +50,13 @@ class InfluenceParamsObject(
val isUnattributedEnabled: Boolean? = null,
)

class FCMParamsObject(
internal 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,
)
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ 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
Expand Down Expand Up @@ -57,6 +58,16 @@ 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"),
Expand All @@ -75,6 +86,7 @@ internal class ParamsBackendService(
opRepoExecutionInterval = responseJson.safeLong("oprepo_execution_interval"),
influenceParams = influenceParams ?: InfluenceParamsObject(),
fcmParams = fcmParams ?: FCMParamsObject(),
remoteLoggingParams = remoteLoggingParams ?: RemoteLoggingParamsObject(),
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.error(
Logging.info(
"scheduleSyncServiceAsJob called JobScheduler.jobScheduler which " +
"triggered an internal null Android error. Skipping job.",
e,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
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

Expand Down Expand Up @@ -36,7 +37,7 @@ class ConfigModel : Model() {
* The API URL String.
*/
var apiUrl: String
get() = getStringProperty(::apiUrl.name) { "https://api.onesignal.com/" }
get() = getStringProperty(::apiUrl.name) { ONESIGNAL_API_BASE_URL }
set(value) {
setStringProperty(::apiUrl.name, value)
}
Expand Down Expand Up @@ -301,6 +302,9 @@ 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,
Expand All @@ -317,6 +321,12 @@ class ConfigModel : Model() {
return model
}

if (property == ::remoteLoggingParams.name) {
val model = RemoteLoggingConfigModel(this, ::remoteLoggingParams.name)
model.initializeFromJson(jsonObject)
return model
}

return null
}
}
Expand Down Expand Up @@ -425,3 +435,34 @@ 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<com.onesignal.debug.LogLevel>(::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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ 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<ConfigModel>(
SimpleModelStore({ ConfigModel() }, "config", prefs),
SimpleModelStore({ ConfigModel() }, CONFIG_NAME_SPACE, prefs),
)
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ 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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
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/"
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ 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,
Expand Down Expand Up @@ -93,7 +96,7 @@ internal class HttpClient(
return@withTimeout makeRequestIODispatcher(url, method, jsonBody, timeout, headers)
}
} catch (e: TimeoutCancellationException) {
Logging.error("HttpClient: Request timed out: $url", e)
Logging.info("HttpClient: Request timed out: $url", e)
return HttpResponse(0, null, e)
} catch (e: Throwable) {
return HttpResponse(0, null, e)
Expand Down Expand Up @@ -135,7 +138,7 @@ internal class HttpClient(
con.useCaches = false
con.connectTimeout = timeout
con.readTimeout = timeout
con.setRequestProperty("SDK-Version", "onesignal/android/" + OneSignalUtils.sdkVersion)
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}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ internal class OperationRepo(
ExecutionResult.FAIL_NORETRY,
ExecutionResult.FAIL_CONFLICT,
-> {
Logging.error("Operation execution failed without retry: $operations")
Logging.warn("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) }
Expand All @@ -279,7 +279,7 @@ internal class OperationRepo(
}
}
ExecutionResult.FAIL_RETRY -> {
Logging.error("Operation execution failed, retrying: $operations")
Logging.info("Operation execution failed, retrying: $operations")
// add back all operations to the front of the queue to be re-executed.
synchronized(queue) {
ops.reversed().forEach {
Expand Down Expand Up @@ -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.error("Operations being delay for: $delayFor ms")
Logging.debug("Operations being delay for: $delayFor ms")
withTimeoutOrNull(delayFor) {
retryWaiter.waitForWake()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,9 @@ interface ITime {
* current time and midnight, January 1, 1970 UTC).
*/
val currentTimeMillis: Long

/**
* Returns how long the app has been running.
*/
val processUptimeMillis: Long
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
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()
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,19 @@ 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
}
}
}
}
Loading
Loading