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 a5043cb401b5..dc05558a9b34 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 @@ -84,8 +85,10 @@ abstract class EditorWebView : ExternalSiteWebView() { } lifecycleScope.launch { - WebViewUtil().setProxyKKPlus(webView) - delay(ONE_SECOND) + if (webView.hasProxyConfig()) { + webView.setProxyKKPlus() + delay(ONE_SECOND) + } if (loadedUrl != webView.url) { webView.loadUrl(loadedUrl) 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 fb7c402ac904..61f97a48f017 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 @@ -70,6 +70,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; @@ -118,7 +119,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; @@ -1262,7 +1262,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 5badaecd64ba..000000000000 --- a/app/src/main/java/com/owncloud/android/utils/WebViewUtil.kt +++ /dev/null @@ -1,99 +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 { - - /** - * From https://stackoverflow.com/a/18453384 - * - * @return - */ - @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 - } - - 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) - } - } - - 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 - } - } - } -}