From ff26a2b697ab0299d94787e648b32398be5ed599 Mon Sep 17 00:00:00 2001 From: daniele-verducci Date: Tue, 13 Oct 2026 12:11:31 +0200 Subject: [PATCH 1/2] Editor webview: Wait for proxy only if there's a proxy configured Signed-off-by: daniele-verducci --- .../com/owncloud/android/ui/activity/EditorWebView.kt | 8 ++++++-- .../main/java/com/owncloud/android/utils/WebViewUtil.kt | 7 ++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/activity/EditorWebView.kt b/app/src/main/java/com/owncloud/android/ui/activity/EditorWebView.kt index c8ece71bd03c..12173eb493d1 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/EditorWebView.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/EditorWebView.kt @@ -83,8 +83,12 @@ abstract class EditorWebView : ExternalSiteWebView() { } lifecycleScope.launch { - WebViewUtil().setProxyKKPlus(webView) - delay(PROXY_SETUP_DELAY) + val wvUtil = WebViewUtil() + + if (wvUtil.hasProxyConfig()) { + wvUtil.setProxyKKPlus(webView) + delay(PROXY_SETUP_DELAY) + } if (loadedUrl != webView.url) { webView.loadUrl(loadedUrl) diff --git a/app/src/main/java/com/owncloud/android/utils/WebViewUtil.kt b/app/src/main/java/com/owncloud/android/utils/WebViewUtil.kt index 5badaecd64ba..133ded14acac 100644 --- a/app/src/main/java/com/owncloud/android/utils/WebViewUtil.kt +++ b/app/src/main/java/com/owncloud/android/utils/WebViewUtil.kt @@ -21,6 +21,8 @@ import java.io.PrintWriter import java.io.StringWriter class WebViewUtil { + val proxyHost = OwnCloudClientManagerFactory.getProxyHost() ?: "" + val proxyPort = OwnCloudClientManagerFactory.getProxyPort() /** * From https://stackoverflow.com/a/18453384 @@ -30,9 +32,6 @@ class WebViewUtil { @SuppressLint("PrivateApi", "DiscouragedPrivateApi") @Suppress("TooGenericExceptionCaught", "NestedBlockDepth") fun setProxyKKPlus(webView: WebView) { - val proxyHost = OwnCloudClientManagerFactory.getProxyHost() - val proxyPort = OwnCloudClientManagerFactory.getProxyPort() - if (TextUtils.isEmpty(proxyHost) || proxyPort <= 0) { return } @@ -79,6 +78,8 @@ class WebViewUtil { } } + fun hasProxyConfig(): Boolean = OwnCloudClientManagerFactory.getProxyHost().isNotEmpty() + companion object { private const val PROXY_TAG = "PROXY" private const val WEB_VIEW_TAG = "WEB_VIEW" From 5811f2235b7db37c875cea752d958718436bf3c3 Mon Sep 17 00:00:00 2001 From: daniele-verducci Date: Wed, 21 Oct 2026 10:23:02 +0200 Subject: [PATCH 2/2] Refactored WebViewUtil into WebViewExtensions Signed-off-by: daniele-verducci --- .../utils/extensions/WebViewExtensions.kt | 91 ++++++++++++++++ .../authentication/AuthenticatorActivity.java | 6 +- .../android/ui/activity/EditorWebView.kt | 9 +- .../ui/activity/ExternalSiteWebView.kt | 7 +- .../ui/fragment/OCFileListFragment.java | 4 +- .../com/owncloud/android/utils/WebViewUtil.kt | 100 ------------------ 6 files changed, 104 insertions(+), 113 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/utils/extensions/WebViewExtensions.kt delete mode 100644 app/src/main/java/com/owncloud/android/utils/WebViewUtil.kt diff --git a/app/src/main/java/com/nextcloud/utils/extensions/WebViewExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/WebViewExtensions.kt new file mode 100644 index 000000000000..92994779c085 --- /dev/null +++ b/app/src/main/java/com/nextcloud/utils/extensions/WebViewExtensions.kt @@ -0,0 +1,91 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.utils.extensions + +import android.annotation.SuppressLint +import android.content.Context +import android.content.Intent +import android.net.Proxy +import android.text.TextUtils +import android.util.ArrayMap +import android.util.Log +import android.webkit.WebView +import androidx.webkit.WebViewCompat +import com.owncloud.android.lib.common.OwnCloudClientManagerFactory +import java.io.PrintWriter +import java.io.StringWriter + +private const val PROXY_TAG = "PROXY" +private const val WEB_VIEW_TAG = "WEB_VIEW" + +fun WebView.getProxyHost() = OwnCloudClientManagerFactory.getProxyHost() ?: "" +fun WebView.getProxyPort() = OwnCloudClientManagerFactory.getProxyPort() + +/** + * From https://stackoverflow.com/a/18453384 + * + * @return + */ +@SuppressLint("PrivateApi", "DiscouragedPrivateApi") +@Suppress("TooGenericExceptionCaught", "NestedBlockDepth") +fun WebView.setProxyKKPlus() { + if (TextUtils.isEmpty(getProxyHost()) || getProxyPort() <= 0) { + return + } + + val applicationClassName = "android.app.Application" + Log.d(PROXY_TAG, "Setting proxy with >= 4.4 API.") + + val appContext = context.applicationContext + + System.setProperty("http.proxyHost", getProxyHost()) + System.setProperty("http.proxyPort", getProxyPort().toString()) + System.setProperty("https.proxyHost", getProxyHost()) + System.setProperty("https.proxyPort", getProxyPort().toString()) + try { + val applicationClass = Class.forName(applicationClassName) + val loadedApkField = applicationClass.getField("mLoadedApk") + loadedApkField.isAccessible = true + val loadedApk = loadedApkField[appContext] + val loadedApkCls = Class.forName("android.app.LoadedApk") + val receiversField = loadedApkCls.getDeclaredField("mReceivers") + receiversField.isAccessible = true + val receivers = receiversField[loadedApk] as ArrayMap<*, *> + for (receiverMap in receivers.values) { + for (rec in (receiverMap as ArrayMap<*, *>).keys) { + val clazz: Class<*> = rec.javaClass + if (clazz.name.contains("ProxyChangeListener")) { + val onReceiveMethod = clazz.getDeclaredMethod( + "onReceive", + Context::class.java, + Intent::class.java + ) + val intent = Intent(Proxy.PROXY_CHANGE_ACTION) + onReceiveMethod.invoke(rec, appContext, intent) + } + } + } + Log.d(PROXY_TAG, "Setting proxy with >= 4.4 API successful!") + } catch (e: Exception) { + val sw = StringWriter() + e.printStackTrace(PrintWriter(sw)) + val exceptionAsString = sw.toString() + e.message?.let { Log.v(PROXY_TAG, it) } + Log.v(PROXY_TAG, exceptionAsString) + } +} + +fun WebView.hasProxyConfig(): Boolean = getProxyHost().isNotEmpty() + +@Suppress("TooGenericExceptionCaught") +fun Context.isWebViewAvailable(): Boolean = try { + WebViewCompat.getCurrentWebViewPackage(this) != null +} catch (e: Exception) { + Log.w(WEB_VIEW_TAG, "WebView provider cannot be resolved: ${e.message}") + false +} diff --git a/app/src/main/java/com/owncloud/android/authentication/AuthenticatorActivity.java b/app/src/main/java/com/owncloud/android/authentication/AuthenticatorActivity.java index dd720ccc985f..03b929999bfb 100644 --- a/app/src/main/java/com/owncloud/android/authentication/AuthenticatorActivity.java +++ b/app/src/main/java/com/owncloud/android/authentication/AuthenticatorActivity.java @@ -67,6 +67,7 @@ import com.nextcloud.utils.RawResourceReader; import com.nextcloud.utils.SnackbarUtil; import com.nextcloud.utils.extensions.BundleExtensionsKt; +import com.nextcloud.utils.extensions.WebViewExtensionsKt; import com.nextcloud.utils.mdm.MDMConfig; import com.nextcloud.utils.text.LinkFormatter; import com.owncloud.android.MainApp; @@ -104,7 +105,6 @@ import com.owncloud.android.ui.dialog.SslUntrustedCertDialog.OnSslUntrustedCertListener; import com.owncloud.android.utils.ErrorMessageAdapter; import com.owncloud.android.utils.PermissionUtil; -import com.owncloud.android.utils.WebViewUtil; import com.owncloud.android.utils.theme.ViewThemeUtils; import java.net.URLDecoder; @@ -908,7 +908,7 @@ protected void onNewIntent(Intent intent) { private void initSimpleSignupLogin() { viewThemeUtils.platform.colorCircularProgressBar(accountSetupWebviewBinding.loginWebviewProgressBar, ColorRole.ON_PRIMARY_CONTAINER); accountSetupWebviewBinding.loginWebview.setVisibility(View.GONE); - new WebViewUtil().setProxyKKPlus(accountSetupWebviewBinding.loginWebview); + WebViewExtensionsKt.setProxyKKPlus(accountSetupWebviewBinding.loginWebview); accountSetupWebviewBinding.loginWebview.getSettings().setAllowFileAccess(false); accountSetupWebviewBinding.loginWebview.getSettings().setJavaScriptEnabled(true); @@ -921,7 +921,7 @@ private void initSimpleSignupLogin() { Map headers = new HashMap<>(); headers.put(RemoteOperation.OCS_API_HEADER, RemoteOperation.OCS_API_HEADER_VALUE); - new WebViewUtil().setProxyKKPlus(accountSetupWebviewBinding.loginWebview); + WebViewExtensionsKt.setProxyKKPlus(accountSetupWebviewBinding.loginWebview); accountSetupWebviewBinding.loginWebview.loadUrl(getString(R.string.provider_registration_server), headers); accountSetupWebviewBinding.loginFlowV2.loginFlowInfoV2.setVisibility(View.GONE); diff --git a/app/src/main/java/com/owncloud/android/ui/activity/EditorWebView.kt b/app/src/main/java/com/owncloud/android/ui/activity/EditorWebView.kt index 12173eb493d1..28da377c0c25 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/EditorWebView.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/EditorWebView.kt @@ -27,7 +27,9 @@ import com.nextcloud.client.account.User import com.nextcloud.utils.SnackbarUtil import com.nextcloud.utils.extensions.getParcelableArgument import com.nextcloud.utils.extensions.getSmallThumbnail +import com.nextcloud.utils.extensions.hasProxyConfig import com.nextcloud.utils.extensions.isPNG +import com.nextcloud.utils.extensions.setProxyKKPlus import com.nextcloud.utils.thumbnail.VideoOverlayGenerator import com.owncloud.android.R import com.owncloud.android.databinding.RichdocumentsWebviewBinding @@ -36,7 +38,6 @@ import com.owncloud.android.datamodel.SyncedFolderObserver import com.owncloud.android.ui.asynctasks.TextEditorLoadUrlTask import com.owncloud.android.utils.MimeTypeUtil import com.owncloud.android.utils.RichDocumentDownloader -import com.owncloud.android.utils.WebViewUtil import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlin.time.Duration.Companion.seconds @@ -83,10 +84,8 @@ abstract class EditorWebView : ExternalSiteWebView() { } lifecycleScope.launch { - val wvUtil = WebViewUtil() - - if (wvUtil.hasProxyConfig()) { - wvUtil.setProxyKKPlus(webView) + if (webView.hasProxyConfig()) { + webView.setProxyKKPlus() delay(PROXY_SETUP_DELAY) } diff --git a/app/src/main/java/com/owncloud/android/ui/activity/ExternalSiteWebView.kt b/app/src/main/java/com/owncloud/android/ui/activity/ExternalSiteWebView.kt index 4ce4b705a7a6..901d8a51aa1d 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/ExternalSiteWebView.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/ExternalSiteWebView.kt @@ -23,12 +23,13 @@ import androidx.drawerlayout.widget.DrawerLayout import com.nextcloud.client.utils.IntentUtil import com.nextcloud.utils.RawResourceReader import com.nextcloud.utils.SnackbarUtil +import com.nextcloud.utils.extensions.isWebViewAvailable +import com.nextcloud.utils.extensions.setProxyKKPlus import com.owncloud.android.MainApp import com.owncloud.android.R import com.owncloud.android.databinding.ExternalsiteWebviewBinding import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.ui.NextcloudWebViewClient -import com.owncloud.android.utils.WebViewUtil import edu.umd.cs.findbugs.annotations.SuppressFBWarnings /** @@ -47,7 +48,7 @@ open class ExternalSiteWebView : FileActivity() { final override fun onCreate(savedInstanceState: Bundle?) { Log_OC.v(TAG, "onCreate() start") - if (!WebViewUtil.available(this)) { + if (!this.isWebViewAvailable()) { super.onCreate(savedInstanceState) SnackbarUtil.show(this, R.string.webview_not_available) finish() @@ -87,7 +88,7 @@ open class ExternalSiteWebView : FileActivity() { setupWebSettings(webView.settings) webView.webViewClient = createWebViewClient() - WebViewUtil().setProxyKKPlus(webView) + webView.setProxyKKPlus() url?.let { webView.loadUrl(it) } } diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java index 41f802839c49..10649db1553f 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -69,6 +69,7 @@ import com.nextcloud.utils.extensions.OCFileExtensionsKt; import com.nextcloud.utils.extensions.OCFileListFragmentExtensionsKt; import com.nextcloud.utils.extensions.ViewExtensionsKt; +import com.nextcloud.utils.extensions.WebViewExtensionsKt; import com.nextcloud.utils.fileNameValidator.FileNameValidator; import com.nextcloud.utils.thumbnail.ThumbnailGenerator; import com.nextcloud.utils.view.FastScrollUtils; @@ -117,7 +118,6 @@ import com.owncloud.android.utils.FileSortOrder; import com.owncloud.android.utils.FileStorageUtils; import com.owncloud.android.utils.PermissionUtil; -import com.owncloud.android.utils.WebViewUtil; import com.owncloud.android.utils.theme.ThemeUtils; import org.greenrobot.eventbus.EventBus; @@ -1239,7 +1239,7 @@ private void handlePendingDownloadFile(OCFile file) { return; } - boolean webViewAvailable = WebViewUtil.available(context); + boolean webViewAvailable = WebViewExtensionsKt.isWebViewAvailable(context); if (!file.isEncrypted() && mContainerActivity instanceof FileDisplayActivity fda && fda.canPreviewInAudioPlayer(file)) { setFabVisible(false); diff --git a/app/src/main/java/com/owncloud/android/utils/WebViewUtil.kt b/app/src/main/java/com/owncloud/android/utils/WebViewUtil.kt deleted file mode 100644 index 133ded14acac..000000000000 --- a/app/src/main/java/com/owncloud/android/utils/WebViewUtil.kt +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2023 Alper Ozturk - * SPDX-FileCopyrightText: 2023 Nextcloud GmbH - * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only - */ -package com.owncloud.android.utils - -import android.annotation.SuppressLint -import android.content.Context -import android.content.Intent -import android.net.Proxy -import android.text.TextUtils -import android.util.ArrayMap -import android.util.Log -import android.webkit.WebView -import androidx.webkit.WebViewCompat -import com.owncloud.android.lib.common.OwnCloudClientManagerFactory -import java.io.PrintWriter -import java.io.StringWriter - -class WebViewUtil { - val proxyHost = OwnCloudClientManagerFactory.getProxyHost() ?: "" - val proxyPort = OwnCloudClientManagerFactory.getProxyPort() - - /** - * From https://stackoverflow.com/a/18453384 - * - * @return - */ - @SuppressLint("PrivateApi", "DiscouragedPrivateApi") - @Suppress("TooGenericExceptionCaught", "NestedBlockDepth") - fun setProxyKKPlus(webView: WebView) { - if (TextUtils.isEmpty(proxyHost) || proxyPort <= 0) { - return - } - - val applicationClassName = "android.app.Application" - Log.d(PROXY_TAG, "Setting proxy with >= 4.4 API.") - - val appContext = webView.context.applicationContext - - System.setProperty("http.proxyHost", proxyHost) - System.setProperty("http.proxyPort", proxyPort.toString()) - System.setProperty("https.proxyHost", proxyHost) - System.setProperty("https.proxyPort", proxyPort.toString()) - try { - val applicationClass = Class.forName(applicationClassName) - val loadedApkField = applicationClass.getField("mLoadedApk") - loadedApkField.isAccessible = true - val loadedApk = loadedApkField[appContext] - val loadedApkCls = Class.forName("android.app.LoadedApk") - val receiversField = loadedApkCls.getDeclaredField("mReceivers") - receiversField.isAccessible = true - val receivers = receiversField[loadedApk] as ArrayMap<*, *> - for (receiverMap in receivers.values) { - for (rec in (receiverMap as ArrayMap<*, *>).keys) { - val clazz: Class<*> = rec.javaClass - if (clazz.name.contains("ProxyChangeListener")) { - val onReceiveMethod = clazz.getDeclaredMethod( - "onReceive", - Context::class.java, - Intent::class.java - ) - val intent = Intent(Proxy.PROXY_CHANGE_ACTION) - onReceiveMethod.invoke(rec, appContext, intent) - } - } - } - Log.d(PROXY_TAG, "Setting proxy with >= 4.4 API successful!") - } catch (e: Exception) { - val sw = StringWriter() - e.printStackTrace(PrintWriter(sw)) - val exceptionAsString = sw.toString() - e.message?.let { Log.v(PROXY_TAG, it) } - Log.v(PROXY_TAG, exceptionAsString) - } - } - - fun hasProxyConfig(): Boolean = OwnCloudClientManagerFactory.getProxyHost().isNotEmpty() - - companion object { - private const val PROXY_TAG = "PROXY" - private const val WEB_VIEW_TAG = "WEB_VIEW" - - @JvmStatic - @Suppress("TooGenericExceptionCaught") - fun available(context: Context?): Boolean { - val context = context ?: return false - - return try { - WebViewCompat.getCurrentWebViewPackage(context) != null - } catch (e: Exception) { - Log.w(WEB_VIEW_TAG, "WebView provider cannot be resolved: ${e.message}") - false - } - } - } -}