Skip to content
Open
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
32 changes: 15 additions & 17 deletions OpenPanelSDK/src/main/java/com/dev/openpanelsdk/OpenPanel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,23 +53,20 @@ class OpenPanel(private val context: Context, private val options: Options) {
}
}

private fun getUserAgent(): String {
return try {
WebView(context).settings.userAgentString
?: (System.getProperty("http.agent")?.toString() ?: "")
}catch (_:Exception){
""
}
}
private fun getUserAgent(): String? = runCatching {
WebView(context).settings.userAgentString
}.getOrNull() ?: runCatching {
System.getProperty("http.agent")
}.getOrNull()
Comment on lines +56 to +60
Copy link

@coderabbitai coderabbitai bot Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Android WebSettings.getDefaultUserAgent static method API level documentation

💡 Result:

WebSettings.getDefaultUserAgent(Context context) is a static method in android.webkit.WebSettings that returns the default User-Agent string used by a WebView. It was added in API level 17 (Android 4.2). An individual WebView instance may use a different UA if you override it via WebSettings.setUserAgentString(String). [1][2]

Docs (API reference): [2]

Signature

  • Java: public static String getDefaultUserAgent (Context context) [2]
  • Kotlin: open static fun getDefaultUserAgent(context: Context!): String! [1]

🏁 Script executed:

cat -n OpenPanelSDK/src/main/java/com/dev/openpanelsdk/OpenPanel.kt | sed -n '50,75p'

Repository: Openpanel-dev/kotlin-sdk

Length of output: 1045


🏁 Script executed:

fd -e gradle -e properties | head -20

Repository: Openpanel-dev/kotlin-sdk

Length of output: 85


🏁 Script executed:

find . -name "build.gradle*" -o -name "gradle.properties" | xargs grep -l "minSdkVersion\|compileSdk" 2>/dev/null | head -5

Repository: Openpanel-dev/kotlin-sdk

Length of output: 122


🏁 Script executed:

cat OpenPanelSDK/build.gradle.kts | grep -i "minsdk\|compilesdk\|targetversion" -A 1

Repository: Openpanel-dev/kotlin-sdk

Length of output: 112


🏁 Script executed:

cat app/build.gradle.kts | grep -i "minsdk\|compilesdk\|targetversion" -A 1

Repository: Openpanel-dev/kotlin-sdk

Length of output: 134


Replace WebView(context) instantiation with WebSettings.getDefaultUserAgent(context) to avoid resource leak.

The current code instantiates a full WebView instance just to retrieve the user-agent string, and the instance is never destroyed — this leaks native resources. WebSettings.getDefaultUserAgent(context) is a static method (available since API 17) that returns the default UA string without allocating a WebView. Since the SDK's minSdk is 24, this is fully compatible.

Proposed fix
-    private fun getUserAgent(): String? = runCatching {
-        WebView(context).settings.userAgentString
-    }.getOrNull() ?: runCatching {
+    private fun getUserAgent(): String? = runCatching {
+        WebSettings.getDefaultUserAgent(context)
+    }.getOrNull() ?: runCatching {
         System.getProperty("http.agent")
     }.getOrNull()
🤖 Prompt for AI Agents
In `@OpenPanelSDK/src/main/java/com/dev/openpanelsdk/OpenPanel.kt` around lines 56
- 60, The getUserAgent() implementation currently creates a WebView via
WebView(context) to read settings.userAgentString which leaks native resources;
replace that WebView usage with WebSettings.getDefaultUserAgent(context)
(available since API 17, safe given minSdk 24) and keep the existing fallback to
System.getProperty("http.agent") so getUserAgent() first tries
WebSettings.getDefaultUserAgent(context) inside runCatching and then
.getOrNull() ?: runCatching { System.getProperty("http.agent") }.getOrNull();
ensure no WebView is instantiated anywhere in getUserAgent().

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is reasonable suggestion

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yzheka, glad you find it helpful! This change will prevent native resource leaks while still providing the same functionality. Let me know if you need any assistance with the implementation.

(\_/)
( •_•)
/ > 🔧



init {
val defaultHeaders = mutableMapOf(
"openpanel-client-id" to options.clientId,
"openpanel-sdk-name" to "kotlin",
"openpanel-sdk-version" to sdkVersion,
"user-agent" to getUserAgent()
"openpanel-sdk-version" to sdkVersion
)
getUserAgent()?.let { defaultHeaders["user-agent"] = it }
// Fetch system information
mSystemInformation = SystemInformation.getInstance(context)

Expand Down Expand Up @@ -157,7 +154,8 @@ class OpenPanel(private val context: Context, private val options: Options) {
flush()

val mergedTraits = (globalProperties.toMap() + (traits ?: emptyMap())).toMutableMap()
send(IdentifyPayload(
send(
IdentifyPayload(
profileId = profileId,
firstName = mergedTraits["firstName"] as? String,
lastName = mergedTraits["lastName"] as? String,
Expand Down Expand Up @@ -199,15 +197,15 @@ class OpenPanel(private val context: Context, private val options: Options) {

private fun getDefaultEventProperties(): Map<String, Any> {
val ret = mutableMapOf<String, Any>()

ret["__osVersion"] = Build.VERSION.RELEASE
ret["__brand"] = Build.BRAND
ret["__model"] = Build.MODEL

val displayMetrics = mSystemInformation?.displayMetrics
ret["__screenDpi"] = displayMetrics?.densityDpi ?: null
ret["__screenHeight"] = displayMetrics?.heightPixels ?: null
ret["__screenWidth"] = displayMetrics?.widthPixels ?: null
displayMetrics?.densityDpi?.let { ret["__screenDpi"] = it }
displayMetrics?.heightPixels?.let { ret["__screenHeight"] = it }
displayMetrics?.widthPixels?.let { ret["__screenWidth"] = it }

val applicationVersionName = mSystemInformation?.appVersionName
if (applicationVersionName != null) {
Expand Down Expand Up @@ -383,7 +381,7 @@ class Api(private val config: Config) {

private fun logVerbose(message: String) {
if (config.verbose) {
Log.d("OpenPanel","OpenPanel: $message")
Log.d("OpenPanel", "OpenPanel: $message")
}
}

Expand Down Expand Up @@ -418,7 +416,7 @@ class Api(private val config: Config) {

connection.doOutput = true
connection.outputStream.use { it.write(data.toString().toByteArray()) }
logVerbose("Sending data: ${data.toString()}")
logVerbose("Sending data: $data")

val responseCode = connection.responseCode
logVerbose("Response code: $responseCode")
Expand Down