@@ -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
@@ -40,7 +40,13 @@ import com.outsystems.plugins.inappbrowser.osinappbrowserlib.OSIABEvents.OSIABWe
4040import com.outsystems.plugins.inappbrowser.osinappbrowserlib.R
4141import com.outsystems.plugins.inappbrowser.osinappbrowserlib.models.OSIABToolbarPosition
4242import com.outsystems.plugins.inappbrowser.osinappbrowserlib.models.OSIABWebViewOptions
43+ import kotlinx.coroutines.Dispatchers
4344import kotlinx.coroutines.launch
45+ import kotlinx.coroutines.withContext
46+ import java.io.File
47+ import java.io.IOException
48+ import java.net.HttpURLConnection
49+ import java.net.URL
4450
4551class OSIABWebViewActivity : AppCompatActivity () {
4652
@@ -88,6 +94,11 @@ class OSIABWebViewActivity : AppCompatActivity() {
8894 // for back navigation
8995 private lateinit var onBackPressedCallback: OnBackPressedCallback
9096
97+ private val PDF_VIEWER_URL_PREFIX = " file:///android_asset/pdfjs/web/viewer.html?file="
98+ // the original URL of the PDF file, used to display it correctly in the view
99+ // and to send the correct URL in the browserPageNavigationCompleted event
100+ private var originalUrl: String? = null
101+
91102 companion object {
92103 const val WEB_VIEW_URL_EXTRA = " WEB_VIEW_URL_EXTRA"
93104 const val WEB_VIEW_OPTIONS_EXTRA = " WEB_VIEW_OPTIONS_EXTRA"
@@ -173,7 +184,7 @@ class OSIABWebViewActivity : AppCompatActivity() {
173184
174185 setupWebView()
175186 if (urlToOpen != null ) {
176- webView.loadUrl (urlToOpen, customHeaders ? : emptyMap() )
187+ handleLoadUrl (urlToOpen, customHeaders)
177188 showLoadingScreen()
178189 }
179190
@@ -206,6 +217,71 @@ class OSIABWebViewActivity : AppCompatActivity() {
206217 }
207218 }
208219
220+ private fun handleLoadUrl (url : String , additionalHttpHeaders : Map <String , String >? = null) {
221+ lifecycleScope.launch(Dispatchers .IO ) {
222+ if (isContentTypeApplicationPdf(url)) {
223+ val pdfFile = try { downloadPdfToCache(url) } catch (_: IOException ) { null }
224+ if (pdfFile != null ) {
225+ withContext(Dispatchers .Main ) {
226+ webView.stopLoading()
227+ originalUrl = url
228+ val pdfJsUrl =
229+ PDF_VIEWER_URL_PREFIX + Uri .encode(" file://${pdfFile.absolutePath} " )
230+ webView.loadUrl(pdfJsUrl)
231+ }
232+ return @launch
233+ }
234+ }
235+
236+ withContext(Dispatchers .Main ) {
237+ webView.loadUrl(url, additionalHttpHeaders ? : emptyMap())
238+ }
239+ }
240+ }
241+
242+ fun isContentTypeApplicationPdf (urlString : String ): Boolean {
243+ return try {
244+ if (checkPdfByRequest(urlString, method = " HEAD" )) {
245+ true
246+ } else {
247+ checkPdfByRequest(urlString, method = " GET" )
248+ }
249+ } catch (_: Exception ) {
250+ false
251+ }
252+ }
253+
254+ private fun checkPdfByRequest (urlString : String , method : String ): Boolean {
255+ var conn: HttpURLConnection ? = null
256+ return try {
257+ conn = (URL (urlString).openConnection() as ? HttpURLConnection )
258+ conn?.run {
259+ instanceFollowRedirects = true
260+ requestMethod = method
261+ if (method == " GET" ) {
262+ setRequestProperty(" Range" , " bytes=0-0" )
263+ }
264+ connect()
265+ val type = contentType?.lowercase()
266+ val disposition = getHeaderField(" Content-Disposition" )?.lowercase()
267+ type == " application/pdf" ||
268+ (type.isNullOrEmpty() && disposition?.contains(" .pdf" ) == true )
269+ } ? : false
270+ } finally {
271+ conn?.disconnect()
272+ }
273+ }
274+
275+ private fun downloadPdfToCache (url : String ): File {
276+ val pdfFile = File (cacheDir, " temp_${System .currentTimeMillis()} .pdf" )
277+ URL (url).openStream().use { input ->
278+ pdfFile.outputStream().use { output ->
279+ input.copyTo(output)
280+ }
281+ }
282+ return pdfFile
283+ }
284+
209285 /* *
210286 * Helper function to update navigation button states
211287 */
@@ -232,19 +308,24 @@ class OSIABWebViewActivity : AppCompatActivity() {
232308 * It also deals with URLs that are opened withing the WebView.
233309 */
234310 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
311+ webView.settings.apply {
312+ javaScriptEnabled = true
313+ javaScriptCanOpenWindowsAutomatically = true
314+ databaseEnabled = true
315+ domStorageEnabled = true
316+ loadWithOverviewMode = true
317+ useWideViewPort = true
318+ allowFileAccess = true
319+ allowFileAccessFromFileURLs = true
320+ allowUniversalAccessFromFileURLs = true
241321
242- if (! options.customUserAgent.isNullOrEmpty())
243- webView.settings. userAgentString = options.customUserAgent
322+ if (! options.customUserAgent.isNullOrEmpty())
323+ userAgentString = options.customUserAgent
244324
245- // get webView settings that come from options
246- webView.settings.builtInZoomControls = options.allowZoom
247- webView.settings.mediaPlaybackRequiresUserGesture = options.mediaPlaybackRequiresUserAction
325+ // get webView settings that come from options
326+ builtInZoomControls = options.allowZoom
327+ mediaPlaybackRequiresUserGesture = options.mediaPlaybackRequiresUserAction
328+ }
248329
249330 // setup WebViewClient and WebChromeClient
250331 webView.webViewClient =
@@ -321,11 +402,17 @@ class OSIABWebViewActivity : AppCompatActivity() {
321402 }
322403
323404 override fun onPageFinished (view : WebView ? , url : String? ) {
405+ val resolvedUrl = when {
406+ url == null -> null
407+ url.startsWith(PDF_VIEWER_URL_PREFIX ) && originalUrl != null -> originalUrl
408+ else -> url
409+ }
410+
324411 if (isFirstLoad && ! hasLoadError) {
325412 sendWebViewEvent(OSIABEvents .BrowserPageLoaded (browserId))
326413 isFirstLoad = false
327414 } else if (! hasLoadError) {
328- sendWebViewEvent(OSIABEvents .BrowserPageNavigationCompleted (browserId, url ))
415+ sendWebViewEvent(OSIABEvents .BrowserPageNavigationCompleted (browserId, resolvedUrl ))
329416 }
330417
331418 // set back to false so that the next successful load
@@ -335,7 +422,7 @@ class OSIABWebViewActivity : AppCompatActivity() {
335422 // store cookies after page finishes loading
336423 storeCookies()
337424 if (hasNavigationButtons) updateNavigationButtons()
338- if (showURL) urlText.text = url
425+ if (showURL) urlText.text = resolvedUrl
339426 currentUrl = url
340427 super .onPageFinished(view, url)
341428 }
@@ -368,7 +455,7 @@ class OSIABWebViewActivity : AppCompatActivity() {
368455 }
369456 // handle every http and https link by loading it in the WebView
370457 urlString.startsWith(" http:" ) || urlString.startsWith(" https:" ) -> {
371- view?.loadUrl (urlString)
458+ handleLoadUrl (urlString)
372459 if (showURL) urlText.text = urlString
373460 true
374461 }
@@ -646,7 +733,7 @@ class OSIABWebViewActivity : AppCompatActivity() {
646733 return findViewById<Button ?>(R .id.reload_button).apply {
647734 setOnClickListener {
648735 currentUrl?.let {
649- webView.loadUrl (it)
736+ handleLoadUrl (it)
650737 showLoadingScreen()
651738 }
652739 }
0 commit comments