From bab03b6c73da5cced1b9a08a0200000e753aa5a0 Mon Sep 17 00:00:00 2001 From: OneWay Date: Sun, 30 Aug 2026 00:39:04 +0800 Subject: [PATCH] fix: retry dictionary downloads on network-switch errors Downloads killed by WiFi/mobile transitions failed with the generic "Dictionary download failed" message because UnknownHostException, ConnectException, unexpected end of stream, and closed-socket errors were not classified as network errors and were never retried. Classify them by exception type and message so the retry loop engages and exhaustion reports the network-specific message. Bump OkHttp 5.4.0 to 5.5.0: a throwing system proxy selector now falls back to a direct connection instead of failing the call, complementing the download resilience fix. --- .../research/core/util/NetworkErrorExtensions.kt | 11 ++++++++++- gradle/libs.versions.toml | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/example/research/core/util/NetworkErrorExtensions.kt b/app/src/main/java/com/example/research/core/util/NetworkErrorExtensions.kt index 41493c6..fe55a20 100644 --- a/app/src/main/java/com/example/research/core/util/NetworkErrorExtensions.kt +++ b/app/src/main/java/com/example/research/core/util/NetworkErrorExtensions.kt @@ -40,6 +40,10 @@ fun Throwable.isNetworkError(): Boolean { if (isSslHandshakeError()) return true return when (this) { + is java.net.UnknownHostException, + is java.net.ConnectException, + is java.net.NoRouteToHostException, + is java.net.BindException -> true is java.io.IOException -> { val message = message ?: "" message.contains("connection", ignoreCase = true) || @@ -49,9 +53,14 @@ fun Throwable.isNetworkError(): Boolean { message.contains("unreachable", ignoreCase = true) || message.contains("no route", ignoreCase = true) || message.contains("broken pipe", ignoreCase = true) || + message.contains("resolve host", ignoreCase = true) || + message.contains("unexpected end of stream", ignoreCase = true) || + message.contains("socket closed", ignoreCase = true) || message.contains("ECONNRESET", ignoreCase = true) || message.contains("ECONNREFUSED", ignoreCase = true) || - message.contains("ENETUNREACH", ignoreCase = true) + message.contains("ECONNABORTED", ignoreCase = true) || + message.contains("ENETUNREACH", ignoreCase = true) || + message.contains("EHOSTUNREACH", ignoreCase = true) } else -> false } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 995b01b..735f249 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ datastore_preferences = "1.2.1" documentfile = "1.1.0" kotlin = "2.4.10" lifecycle_runtime_ktx = "2.11.0" -okhttp = "5.4.0" +okhttp = "5.5.0" paging = "3.5.1" profileinstaller = "1.4.1" kotlinx_coroutines = "1.11.0"