@@ -4,12 +4,12 @@ import android.Manifest
44import android.app.Activity
55import android.content.Intent
66import android.content.pm.PackageManager
7+ import android.graphics.Bitmap
78import android.net.Uri
89import android.os.Build
910import android.os.Bundle
10- import android.view.Gravity
1111import android.util.Log
12- import android.graphics.Bitmap
12+ import android.view.Gravity
1313import android.view.View
1414import android.webkit.CookieManager
1515import android.webkit.GeolocationPermissions
@@ -38,9 +38,13 @@ import androidx.lifecycle.lifecycleScope
3838import com.outsystems.plugins.inappbrowser.osinappbrowserlib.OSIABEvents
3939import com.outsystems.plugins.inappbrowser.osinappbrowserlib.OSIABEvents.OSIABWebViewEvent
4040import com.outsystems.plugins.inappbrowser.osinappbrowserlib.R
41+ import com.outsystems.plugins.inappbrowser.osinappbrowserlib.helpers.OSIABPdfHelper
4142import com.outsystems.plugins.inappbrowser.osinappbrowserlib.models.OSIABToolbarPosition
4243import com.outsystems.plugins.inappbrowser.osinappbrowserlib.models.OSIABWebViewOptions
44+ import kotlinx.coroutines.Dispatchers
4345import kotlinx.coroutines.launch
46+ import kotlinx.coroutines.withContext
47+ import java.io.IOException
4448
4549class OSIABWebViewActivity : AppCompatActivity () {
4650
@@ -88,6 +92,11 @@ class OSIABWebViewActivity : AppCompatActivity() {
8892 // for back navigation
8993 private lateinit var onBackPressedCallback: OnBackPressedCallback
9094
95+ private val PDF_VIEWER_URL_PREFIX = " file:///android_asset/pdfjs/web/viewer.html?file="
96+ // the original URL of the PDF file, used to display it correctly in the view
97+ // and to send the correct URL in the browserPageNavigationCompleted event
98+ private var originalUrl: String? = null
99+
91100 companion object {
92101 const val WEB_VIEW_URL_EXTRA = " WEB_VIEW_URL_EXTRA"
93102 const val WEB_VIEW_OPTIONS_EXTRA = " WEB_VIEW_OPTIONS_EXTRA"
@@ -173,7 +182,7 @@ class OSIABWebViewActivity : AppCompatActivity() {
173182
174183 setupWebView()
175184 if (urlToOpen != null ) {
176- webView.loadUrl (urlToOpen, customHeaders ? : emptyMap() )
185+ handleLoadUrl (urlToOpen, customHeaders)
177186 showLoadingScreen()
178187 }
179188
@@ -206,6 +215,29 @@ class OSIABWebViewActivity : AppCompatActivity() {
206215 }
207216 }
208217
218+ private fun handleLoadUrl (url : String , additionalHttpHeaders : Map <String , String >? = null) {
219+ lifecycleScope.launch(Dispatchers .IO ) {
220+ if (OSIABPdfHelper .isContentTypeApplicationPdf(url)) {
221+ val pdfFile = try { OSIABPdfHelper .downloadPdfToCache(this @OSIABWebViewActivity, url) } catch (_: IOException ) { null }
222+ if (pdfFile != null ) {
223+ withContext(Dispatchers .Main ) {
224+ webView.stopLoading()
225+ originalUrl = url
226+ val pdfJsUrl =
227+ PDF_VIEWER_URL_PREFIX + Uri .encode(" file://${pdfFile.absolutePath} " )
228+ webView.loadUrl(pdfJsUrl)
229+ }
230+ return @launch
231+ }
232+ }
233+
234+ withContext(Dispatchers .Main ) {
235+ webView.loadUrl(url, additionalHttpHeaders ? : emptyMap())
236+ }
237+ }
238+ }
239+
240+
209241 /* *
210242 * Helper function to update navigation button states
211243 */
@@ -232,19 +264,24 @@ class OSIABWebViewActivity : AppCompatActivity() {
232264 * It also deals with URLs that are opened withing the WebView.
233265 */
234266 private fun setupWebView () {
235- webView.settings.javaScriptEnabled = true
236- webView.settings.javaScriptCanOpenWindowsAutomatically = true
237- webView.settings.databaseEnabled = true
238- webView.settings.domStorageEnabled = true
239- webView.settings.loadWithOverviewMode = true
240- webView.settings.useWideViewPort = true
267+ webView.settings.apply {
268+ javaScriptEnabled = true
269+ javaScriptCanOpenWindowsAutomatically = true
270+ databaseEnabled = true
271+ domStorageEnabled = true
272+ loadWithOverviewMode = true
273+ useWideViewPort = true
274+ allowFileAccess = true
275+ allowFileAccessFromFileURLs = true
276+ allowUniversalAccessFromFileURLs = true
241277
242- if (! options.customUserAgent.isNullOrEmpty())
243- webView.settings. userAgentString = options.customUserAgent
278+ if (! options.customUserAgent.isNullOrEmpty())
279+ userAgentString = options.customUserAgent
244280
245- // get webView settings that come from options
246- webView.settings.builtInZoomControls = options.allowZoom
247- webView.settings.mediaPlaybackRequiresUserGesture = options.mediaPlaybackRequiresUserAction
281+ // get webView settings that come from options
282+ builtInZoomControls = options.allowZoom
283+ mediaPlaybackRequiresUserGesture = options.mediaPlaybackRequiresUserAction
284+ }
248285
249286 // setup WebViewClient and WebChromeClient
250287 webView.webViewClient =
@@ -320,12 +357,35 @@ class OSIABWebViewActivity : AppCompatActivity() {
320357 }
321358 }
322359
360+ var lastPageFinishedUrl: String? = null
361+
323362 override fun onPageFinished (view : WebView ? , url : String? ) {
363+ if (url != null && url == lastPageFinishedUrl && url.startsWith(PDF_VIEWER_URL_PREFIX )) {
364+ // If the url is the same as the last finished URL and it is a PDF viewer URL,
365+ // we do not want to trigger the page finished event again.
366+ // This prevents the event from being sent multiple times
367+ // since PDF.js triggers onPageFinished multiple times during PDF rendering.
368+ return
369+ }
370+ lastPageFinishedUrl = url
371+
372+ val resolvedUrl = when {
373+ url == null -> null
374+ url.startsWith(PDF_VIEWER_URL_PREFIX ) && originalUrl != null -> originalUrl
375+ else -> url
376+ }
377+
324378 if (isFirstLoad && ! hasLoadError) {
325379 sendWebViewEvent(OSIABEvents .BrowserPageLoaded (browserId))
326380 isFirstLoad = false
327381 } else if (! hasLoadError) {
328- sendWebViewEvent(OSIABEvents .BrowserPageNavigationCompleted (browserId, url))
382+ sendWebViewEvent(OSIABEvents .BrowserPageNavigationCompleted (browserId, resolvedUrl))
383+ }
384+
385+ if (url?.startsWith(PDF_VIEWER_URL_PREFIX ) == true && options.clearCache) {
386+ webView.evaluateJavascript(
387+ " localStorage.clear(); sessionStorage.clear();" , null
388+ )
329389 }
330390
331391 // set back to false so that the next successful load
@@ -335,7 +395,7 @@ class OSIABWebViewActivity : AppCompatActivity() {
335395 // store cookies after page finishes loading
336396 storeCookies()
337397 if (hasNavigationButtons) updateNavigationButtons()
338- if (showURL) urlText.text = url
398+ if (showURL) urlText.text = resolvedUrl
339399 currentUrl = url
340400 super .onPageFinished(view, url)
341401 }
@@ -368,7 +428,7 @@ class OSIABWebViewActivity : AppCompatActivity() {
368428 }
369429 // handle every http and https link by loading it in the WebView
370430 urlString.startsWith(" http:" ) || urlString.startsWith(" https:" ) -> {
371- view?.loadUrl (urlString)
431+ handleLoadUrl (urlString)
372432 if (showURL) urlText.text = urlString
373433 true
374434 }
@@ -646,7 +706,7 @@ class OSIABWebViewActivity : AppCompatActivity() {
646706 return findViewById<Button ?>(R .id.reload_button).apply {
647707 setOnClickListener {
648708 currentUrl?.let {
649- webView.loadUrl (it)
709+ handleLoadUrl (it)
650710 showLoadingScreen()
651711 }
652712 }
0 commit comments