diff --git a/app/src/main/java/com/droiddeck/launcher/stores/StoreLoginActivity.kt b/app/src/main/java/com/droiddeck/launcher/stores/StoreLoginActivity.kt index b1f8da0b4..1b5c6ccdd 100644 --- a/app/src/main/java/com/droiddeck/launcher/stores/StoreLoginActivity.kt +++ b/app/src/main/java/com/droiddeck/launcher/stores/StoreLoginActivity.kt @@ -5,6 +5,7 @@ import android.content.res.Configuration import android.graphics.Color import android.net.Uri import android.net.http.SslError +import android.os.Build import android.os.Bundle import android.os.Message import android.util.Log @@ -18,6 +19,7 @@ import android.webkit.WebChromeClient import android.webkit.WebResourceError import android.webkit.WebResourceRequest import android.webkit.WebResourceResponse +import android.webkit.WebSettings import android.webkit.WebView import android.webkit.WebViewClient import android.widget.Button @@ -40,7 +42,10 @@ class StoreLoginActivity : ComponentActivity() { private const val KEY_STATE = "oauth_state" private const val KEY_SIGN_IN_CLEARED = "sign_in_cleared" @Volatile internal var floodColor: Int? = null - internal const val CHROME_UA = "Mozilla/5.0 (Linux; Android 14; SM-S918B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.103 Mobile Safari/537.36" + internal fun chromeUserAgent(webView: String?): String = + webView?.replace(Regex(" Build/[^;)]*"), "")?.replace("; wv)", ")")?.replace(" Version/4.0", "") + ?.takeIf { "Chrome/" in it } + ?: "Mozilla/5.0 (Linux; Android ${Build.VERSION.RELEASE}; ${Build.MODEL}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.103 Mobile Safari/537.36" fun generateState(): String { val bytes = ByteArray(24) @@ -76,6 +81,7 @@ class StoreLoginActivity : ComponentActivity() { private var oauthState: String? = null private val captured = AtomicBoolean(false) private var flooded = false + private val chromeUa by lazy { chromeUserAgent(runCatching { WebSettings.getDefaultUserAgent(this) }.getOrNull()) } private var signInCleared = false private fun dp(value: Int): Int = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value.toFloat(), resources.displayMetrics).toInt() @@ -123,7 +129,7 @@ class StoreLoginActivity : ComponentActivity() { private fun newFlow(state: String): Flow? { val url = clientUrl - if (url != null) return com.droiddeck.launcher.stores.epic.EpicClient.loginFlow(url) + if (url != null) return com.droiddeck.launcher.stores.epic.EpicClient.loginFlow(url, chromeUa) return store?.let { LoginFlows.forStore(it, state) } } @@ -234,7 +240,7 @@ class StoreLoginActivity : ComponentActivity() { override fun onCreateWindow(view: WebView?, isDialog: Boolean, isUserGesture: Boolean, resultMsg: Message?): Boolean { if (resultMsg == null) return false dismissPopup() - val child = newWebView(CHROME_UA, isPopup = true) + val child = newWebView(chromeUa, isPopup = true) popupWebView = child popupHost.addView(child, FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)) popupHost.visibility = View.VISIBLE @@ -306,8 +312,8 @@ class StoreLoginActivity : ComponentActivity() { if (f.isRedirect(url) && !f.readsPage) { complete(view, url); return true } if (!isPopup && store == Store.GOG) { if (GogAuth.isGogHost(uri.host) && view.settings.userAgentString != f.userAgent) { view.settings.userAgentString = f.userAgent; return false } - if (GogAuth.isIdentityProviderHost(uri.host) && view.settings.userAgentString != CHROME_UA) { - view.settings.userAgentString = CHROME_UA + if (GogAuth.isIdentityProviderHost(uri.host) && view.settings.userAgentString != chromeUa) { + view.settings.userAgentString = chromeUa CookieManager.getInstance().setAcceptThirdPartyCookies(view, true) view.loadUrl(url) return true diff --git a/app/src/main/java/com/droiddeck/launcher/stores/epic/EpicClient.kt b/app/src/main/java/com/droiddeck/launcher/stores/epic/EpicClient.kt index 1f8a6ba9d..7f0e86b14 100644 --- a/app/src/main/java/com/droiddeck/launcher/stores/epic/EpicClient.kt +++ b/app/src/main/java/com/droiddeck/launcher/stores/epic/EpicClient.kt @@ -392,10 +392,10 @@ object EpicClient { } } - internal fun loginFlow(url: String): StoreLoginActivity.Flow? { + internal fun loginFlow(url: String, browserAgent: String): StoreLoginActivity.Flow? { val redirect = redirectOf(url) ?: return null val agent = runCatching { EpicNative.call("auth.config")?.optString("webViewUserAgent") }.getOrNull()?.ifEmpty { null } - return ClientLogin(url, redirect, agent ?: StoreLoginActivity.CHROME_UA) + return ClientLogin(url, redirect, agent ?: browserAgent) } private class ClientLogin( diff --git a/app/src/test/java/com/droiddeck/launcher/stores/StoreLoginUserAgentTest.kt b/app/src/test/java/com/droiddeck/launcher/stores/StoreLoginUserAgentTest.kt new file mode 100644 index 000000000..bf0c264f2 --- /dev/null +++ b/app/src/test/java/com/droiddeck/launcher/stores/StoreLoginUserAgentTest.kt @@ -0,0 +1,27 @@ +package com.droiddeck.launcher.stores + +import android.os.Build +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +class StoreLoginUserAgentTest { + @Test fun theDevicesWebViewAgentIsSentAsChrome() { + assertEquals( + "Mozilla/5.0 (Linux; Android 16; NP06J) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.7390.122 Mobile Safari/537.36", + StoreLoginActivity.chromeUserAgent( + "Mozilla/5.0 (Linux; Android 16; NP06J Build/BP2A.250605.031.A3; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/141.0.7390.122 Mobile Safari/537.36", + ), + ) + } + + @Test fun noWebViewAgentFallsBackToChromeWithoutAnotherDevicesModel() { + for (agent in listOf(null, "", "Dalvik/2.1.0 (Linux; U; Android 16)")) { + val ua = StoreLoginActivity.chromeUserAgent(agent) + assertTrue(ua, "Chrome/" in ua) + assertFalse(ua, "SM-S918B" in ua) + assertTrue(ua, "Android ${Build.VERSION.RELEASE}; ${Build.MODEL})" in ua) + } + } +}