Skip to content
Open
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 @@ -32,6 +32,8 @@ class AnkiPackageImporterFragment : PageFragment() {
"import-anki-package$filePath"
}

override fun requiresModernWebView() = true

override fun onCreateWebViewClient(savedInstanceState: Bundle?): PageWebViewClient {
// the back callback is only enabled when import is running and showing progress
val backCallback =
Expand Down
2 changes: 2 additions & 0 deletions AnkiDroid/src/main/java/com/ichi2/anki/pages/CsvImporter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ class CsvImporter : PageFragment() {
"import-csv$filePath"
}

override fun requiresModernWebView() = true

override fun onCreateWebViewClient(savedInstanceState: Bundle?): PageWebViewClient {
// the back callback is only enabled when import is running and showing progress
val backCallback =
Expand Down
12 changes: 11 additions & 1 deletion AnkiDroid/src/main/java/com/ichi2/anki/pages/PageFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@ import androidx.core.net.toUri
import androidx.fragment.app.Fragment
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.progressindicator.CircularProgressIndicator
import com.ichi2.anki.AnkiActivity
import com.ichi2.anki.R
import com.ichi2.anki.workarounds.OnWebViewRecreatedListener
import com.ichi2.anki.workarounds.SafeWebViewLayout
import com.ichi2.themes.Themes
import com.ichi2.utils.checkWebviewVersion
import timber.log.Timber

/**
Expand Down Expand Up @@ -64,6 +66,8 @@ abstract class PageFragment(

protected open fun onWebViewCreated() { }

protected open fun requiresModernWebView(): Boolean = false

/**
* When the webview calls `BridgeCommand("foo")`, the PageFragment execute `bridgeCommands["foo"]`.
* By default, only bridge command is allowed, subclasses must redefine it if they expect bridge commands.
Expand All @@ -73,6 +77,7 @@ abstract class PageFragment(
/**
* Ensures that [pageWebViewClient] can receive `bridgeCommand` requests and execute the command from [bridgeCommands].
*/

private fun setupBridgeCommand(pageWebViewClient: PageWebViewClient) {
if (bridgeCommands.isEmpty()) {
return
Expand Down Expand Up @@ -100,13 +105,18 @@ abstract class PageFragment(
view: View,
savedInstanceState: Bundle?,
) {
val ankiActivity = requireActivity() as AnkiActivity
if (requiresModernWebView() && checkWebviewVersion(ankiActivity)) {
Timber.w("${this::class.simpleName} requires modern WebView (Chrome 90+), aborting load")
ankiActivity.onBackPressedDispatcher.onBackPressed()
return
}
server = AnkiServer(this).also { it.start() }
webViewLayout = view.findViewById(R.id.webview_layout)

view.findViewById<MaterialToolbar>(R.id.toolbar)?.setNavigationOnClickListener {
requireActivity().onBackPressedDispatcher.onBackPressed()
}

setupWebView(savedInstanceState)
}

Expand Down
41 changes: 21 additions & 20 deletions AnkiDroid/src/main/java/com/ichi2/utils/WebViewUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ import com.ichi2.anki.CrashReportService
import com.ichi2.anki.R
import timber.log.Timber

internal const val OLDEST_WORKING_WEBVIEW_VERSION_CODE = 418306960L
internal const val OLDEST_WORKING_WEBVIEW_VERSION = 85
internal const val OLDEST_WORKING_WEBVIEW_VERSION_CODE = 443000000L
internal const val OLDEST_WORKING_WEBVIEW_VERSION = 90

/**
* Shows a dialog if the current WebView version is older than the last supported version.
*/
fun checkWebviewVersion(activity: AnkiActivity) {
val userVisibleCode = getChromeLikeWebViewVersionIfOutdated(activity) ?: return
fun checkWebviewVersion(activity: AnkiActivity): Boolean {
val userVisibleCode = getChromeLikeWebViewVersionIfOutdated(activity) ?: return false

// Provide guidance to the user if the WebView is outdated
val webviewPackageInfo = getAndroidSystemWebViewPackageInfo(activity.packageManager)
Expand All @@ -51,6 +51,7 @@ fun checkWebviewVersion(activity: AnkiActivity) {
Timber.w("WebView is outdated. %s: %s", webviewPackageInfo?.packageName, webviewPackageInfo?.versionName)
showOutdatedWebViewDialog(activity, userVisibleCode, R.string.link_webview_update)
}
return true
}

@MainThread
Expand Down Expand Up @@ -93,33 +94,33 @@ fun checkWebViewVersionComponents(
versionCode: Long,
userAgent: String?,
): Int? {
// Checking the version code works for most webview packages
if (versionCode >= OLDEST_WORKING_WEBVIEW_VERSION_CODE) {
Timber.d(
"WebView is up to date. %s: %s(%s)",
packageName,
webviewVersion,
versionCode.toString(),
)
return null
}

// Sometimes the webview version code appears too old, and the package name does as well,
// but it's a webview that advertises modern capabilities via User-Agent in "Chrome" section
// Our warning is purely advisory, so, let's let those through if User-Agent looks okay
userAgent?.let {
val chromeRegex = """Chrome/(\d+)""".toRegex()
val matchResult = chromeRegex.find(userAgent)?.groupValues?.get(1)
matchResult?.toInt()?.let {
if (it < OLDEST_WORKING_WEBVIEW_VERSION) {
// If we got here, even the User-Agent says it's incompatible, return something
// potentially useful to the user as a browser version
if (it >= OLDEST_WORKING_WEBVIEW_VERSION) {
// If the User-Agent says we are modern, trust it and skip further checks.
return null
} else {
// If the User-Agent is explicitly below the floor, return it immediately.
return it
}
}
}

return null
// Checking the version code works for most webview packages
if (versionCode >= OLDEST_WORKING_WEBVIEW_VERSION_CODE) {
Timber.d(
"WebView is up to date. %s: %s(%s)",
packageName,
webviewVersion,
versionCode.toString(),
)
return null
}
return webviewVersion.split('.').firstOrNull()?.toIntOrNull()
}

private fun showOutdatedWebViewDialog(
Expand Down
22 changes: 22 additions & 0 deletions AnkiDroid/src/test/java/com/ichi2/utils/WebViewUtilsTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,27 @@ class WebViewUtilsTest {
),
equalTo(null),
)
assertThat(
"Should catch old engine (78) in Huawei package even with valid versionCode",
checkWebViewVersionComponents(
"com.huawei.webview",
"12.1.2.322",
450000000L,
"Mozilla/5.0 (Linux; Android 10; CDY-AN90 Build/HUAWEICDY-AN90; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.108 Mobile Safari/537.36",
),
equalTo(78),
)
// Link: https://www.apkmirror.com/apk/huawei/huawei-webview-2/huawei-webview-15-0-4-326-release/
// verified version code is 2113L for 15.0.4.326 by analyzing the manifest
assertThat(
"Huawei v15 with code 21311 should be allowed if UA indicates modern engine (114)",
checkWebViewVersionComponents(
"com.huawei.webview",
"15.0.4.326",
21311L,
"Mozilla/5.0 (Linux; Android 12; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/114.0.5735.196 Mobile Safari/537.36",
),
equalTo(null),
)
}
}