Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ANR getting default user agent #21681

Closed
wants to merge 14 commits into from
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,47 @@ package org.wordpress.android.fluxc.network

import android.content.Context
import android.webkit.WebSettings
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.wordpress.android.util.AppLog
import org.wordpress.android.util.PackageUtils
import javax.inject.Singleton

@SuppressWarnings("SwallowedException", "TooGenericExceptionCaught", "MemberNameEqualsClassName")
class UserAgent(appContext: Context?, appName: String) {
val userAgent: String
@Singleton
@Suppress("MemberNameEqualsClassName")
class UserAgent @JvmOverloads constructor(
private val appContext: Context?,
private val appName: String,
bgDispatcher: CoroutineDispatcher = Dispatchers.Default
) {
/**
* User-Agent string when making HTTP connections, for both API traffic and WebViews.
* Appends "[appName]/version" to WebView's default User-Agent string for the webservers
* to get the full feature list of the browser and serve content accordingly, e.g.:
* "Mozilla/5.0 (Linux; Android 6.0; Android SDK built for x86_64 Build/MASTER; wv)
* AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/44.0.2403.119 Mobile Safari/537.36
* wp-android/4.7"
*/
var userAgent: String = getAppNameVersion()
private set

private val coroutineScope = CoroutineScope(bgDispatcher)

init {
coroutineScope.launch {
initUserAgent()
}
}

/**
* Initializes the User-Agent string.
* This method will be called asynchronously to avoid blocking the main thread,
* because `WebSettings.getDefaultUserAgent()` can be slow.
*/
@Suppress("TooGenericExceptionCaught", "SwallowedException")
private fun initUserAgent() {
// Device's default User-Agent string.
// E.g.:
// "Mozilla/5.0 (Linux; Android 6.0; Android SDK built for x86_64 Build/MASTER; wv)
Expand All @@ -18,17 +52,17 @@ class UserAgent(appContext: Context?, appName: String) {
} catch (e: RuntimeException) {
// `getDefaultUserAgent()` can throw an Exception
// see: https://github.com/wordpress-mobile/WordPress-Android/issues/20147#issuecomment-1961238187
""
AppLog.e(
AppLog.T.UTILS,
"Error getting the user's default User-Agent, ${e.stackTraceToString()}"
)
return
}
// User-Agent string when making HTTP connections, for both API traffic and WebViews.
// Appends "wp-android/version" to WebView's default User-Agent string for the webservers
// to get the full feature list of the browser and serve content accordingly, e.g.:
// "Mozilla/5.0 (Linux; Android 6.0; Android SDK built for x86_64 Build/MASTER; wv)
// AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/44.0.2403.119 Mobile Safari/537.36
// wp-android/4.7"
val appWithVersion = "$appName/${PackageUtils.getVersionName(appContext)}"
userAgent = if (defaultUserAgent.isNotEmpty()) "$defaultUserAgent $appWithVersion" else appWithVersion

userAgent = "$defaultUserAgent ${getAppNameVersion()}"
}

private fun getAppNameVersion() = "$appName/${PackageUtils.getVersionName(appContext)}"

override fun toString(): String = userAgent
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.wordpress.android.fluxc.network

import android.webkit.WebSettings
import kotlinx.coroutines.Dispatchers
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mockStatic
Expand All @@ -22,7 +23,8 @@ class UserAgentTest {
fun testUserAgent() = withMockedPackageUtils {
mockStatic(WebSettings::class.java).use {
whenever(WebSettings.getDefaultUserAgent(context)).thenReturn(USER_AGENT)
val result = UserAgent(context, APP_NAME)
// Use the Unconfined dispatcher to allow the test to run synchronously
val result = UserAgent(context, APP_NAME, bgDispatcher = Dispatchers.Unconfined)
assertEquals("$USER_AGENT $APP_NAME/$APP_VERSION", result.toString())
}
}
Expand All @@ -31,7 +33,8 @@ class UserAgentTest {
fun testDefaultUserAgentFailure() = withMockedPackageUtils {
mockStatic(WebSettings::class.java).use {
whenever(WebSettings.getDefaultUserAgent(context)).thenThrow(RuntimeException(""))
val result = UserAgent(context, APP_NAME)
// Use the Unconfined dispatcher to allow the test to run synchronously
val result = UserAgent(context, APP_NAME, bgDispatcher = Dispatchers.Unconfined)
assertEquals("$APP_NAME/$APP_VERSION", result.toString())
}
}
Expand Down