WebViewUtil.kt 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. /*
  2. * Nextcloud Android client application
  3. *
  4. * @author Alper Ozturk
  5. * Copyright (C) 2023 Alper Ozturk
  6. * Copyright (C) 2023 Nextcloud GmbH
  7. *
  8. * This program is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU Affero General Public License as published by
  10. * the Free Software Foundation, either version 3 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU Affero General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU Affero General Public License
  19. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. */
  21. package com.owncloud.android.utils
  22. import android.annotation.SuppressLint
  23. import android.content.Context
  24. import android.content.Intent
  25. import android.content.pm.PackageManager
  26. import android.net.Proxy
  27. import android.net.Uri
  28. import android.text.TextUtils
  29. import android.util.ArrayMap
  30. import android.util.Log
  31. import android.webkit.WebView
  32. import com.google.android.material.dialog.MaterialAlertDialogBuilder
  33. import com.owncloud.android.R
  34. import com.owncloud.android.lib.common.OwnCloudClientManagerFactory
  35. import java.io.PrintWriter
  36. import java.io.StringWriter
  37. class WebViewUtil(private val context: Context) {
  38. private val packageName = "com.google.android.webview"
  39. fun checkWebViewVersion() {
  40. if (!isWebViewVersionValid()) {
  41. showUpdateDialog()
  42. }
  43. }
  44. private fun isWebViewVersionValid(): Boolean {
  45. val currentWebViewVersion = getCurrentWebViewMajorVersion() ?: return true
  46. val minSupportedWebViewVersion: String = getMinimumSupportedMajorWebViewVersion()
  47. return currentWebViewVersion.toInt() >= minSupportedWebViewVersion.toInt()
  48. }
  49. private fun showUpdateDialog() {
  50. val builder = MaterialAlertDialogBuilder(context)
  51. .setTitle(context.getString(R.string.webview_version_check_alert_dialog_title))
  52. .setMessage(context.getString(R.string.webview_version_check_alert_dialog_message))
  53. .setCancelable(false)
  54. .setPositiveButton(
  55. context.getString(R.string.webview_version_check_alert_dialog_positive_button_title)
  56. ) { _, _ ->
  57. redirectToAndroidSystemWebViewStorePage()
  58. }
  59. val dialog = builder.create()
  60. dialog.show()
  61. }
  62. private fun redirectToAndroidSystemWebViewStorePage() {
  63. val uri = Uri.parse("market://details?id=$packageName")
  64. val intent = Intent(Intent.ACTION_VIEW, uri)
  65. intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
  66. try {
  67. context.startActivity(intent)
  68. } catch (e: android.content.ActivityNotFoundException) {
  69. redirectToPlayStoreWebsiteForAndroidSystemWebView()
  70. }
  71. }
  72. private fun redirectToPlayStoreWebsiteForAndroidSystemWebView() {
  73. val playStoreWebUri = Uri.parse("https://play.google.com/store/apps/details?id=$packageName")
  74. val webIntent = Intent(Intent.ACTION_VIEW, playStoreWebUri)
  75. context.startActivity(webIntent)
  76. }
  77. private fun getCurrentWebViewMajorVersion(): String? {
  78. val pm: PackageManager = context.packageManager
  79. return try {
  80. val pi = pm.getPackageInfo("com.google.android.webview", 0)
  81. val fullVersion = pi.versionName
  82. // Split the version string by "." and get the first part
  83. val versionParts = fullVersion.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }
  84. .toTypedArray()
  85. if (versionParts.isNotEmpty()) {
  86. versionParts[0]
  87. } else {
  88. null
  89. }
  90. } catch (e: PackageManager.NameNotFoundException) {
  91. null
  92. }
  93. }
  94. /**
  95. * Ideally we should fetch from database, reading actual value
  96. * from PlayStore not feasible due to frequently api changes made by
  97. * Google
  98. *
  99. */
  100. private fun getMinimumSupportedMajorWebViewVersion(): String {
  101. return "118"
  102. }
  103. /**
  104. * From https://stackoverflow.com/a/18453384
  105. *
  106. * @return
  107. */
  108. @SuppressLint("PrivateApi", "DiscouragedPrivateApi")
  109. @Suppress("TooGenericExceptionCaught")
  110. fun setProxyKKPlus(webView: WebView) {
  111. val proxyHost = OwnCloudClientManagerFactory.getProxyHost()
  112. val proxyPort = OwnCloudClientManagerFactory.getProxyPort()
  113. if (TextUtils.isEmpty(proxyHost) || proxyPort <= 0) {
  114. return
  115. }
  116. val applicationClassName = "android.app.Application"
  117. Log.d(PROXY_TAG, "Setting proxy with >= 4.4 API.")
  118. val appContext = webView.context.applicationContext
  119. System.setProperty("http.proxyHost", proxyHost)
  120. System.setProperty("http.proxyPort", proxyPort.toString())
  121. System.setProperty("https.proxyHost", proxyHost)
  122. System.setProperty("https.proxyPort", proxyPort.toString())
  123. try {
  124. val applicationClass = Class.forName(applicationClassName)
  125. val loadedApkField = applicationClass.getField("mLoadedApk")
  126. loadedApkField.isAccessible = true
  127. val loadedApk = loadedApkField[appContext]
  128. val loadedApkCls = Class.forName("android.app.LoadedApk")
  129. val receiversField = loadedApkCls.getDeclaredField("mReceivers")
  130. receiversField.isAccessible = true
  131. val receivers = receiversField[loadedApk] as ArrayMap<*, *>
  132. for (receiverMap in receivers.values) {
  133. for (rec in (receiverMap as ArrayMap<*, *>).keys) {
  134. val clazz: Class<*> = rec.javaClass
  135. if (clazz.name.contains("ProxyChangeListener")) {
  136. val onReceiveMethod = clazz.getDeclaredMethod(
  137. "onReceive",
  138. Context::class.java,
  139. Intent::class.java
  140. )
  141. val intent = Intent(Proxy.PROXY_CHANGE_ACTION)
  142. onReceiveMethod.invoke(rec, appContext, intent)
  143. }
  144. }
  145. }
  146. Log.d(PROXY_TAG, "Setting proxy with >= 4.4 API successful!")
  147. } catch (e: Exception) {
  148. val sw = StringWriter()
  149. e.printStackTrace(PrintWriter(sw))
  150. val exceptionAsString = sw.toString()
  151. e.message?.let { Log.v(PROXY_TAG, it) }
  152. Log.v(PROXY_TAG, exceptionAsString)
  153. }
  154. }
  155. companion object {
  156. private const val PROXY_TAG = "PROXY"
  157. }
  158. }