Skip to content

Commit

Permalink
Check WebView support and version
Browse files Browse the repository at this point in the history
  • Loading branch information
Maxr1998 committed Mar 23, 2021
1 parent 83d23d1 commit 5f73db5
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 6 deletions.
15 changes: 15 additions & 0 deletions app/src/main/java/org/jellyfin/mobile/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import android.content.ServiceConnection
import android.os.Bundle
import android.os.IBinder
import android.view.OrientationEventListener
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.flow.collect
Expand All @@ -19,6 +20,7 @@ import org.jellyfin.mobile.player.PlayerFragment
import org.jellyfin.mobile.utils.Constants
import org.jellyfin.mobile.utils.PermissionRequestHelper
import org.jellyfin.mobile.utils.SmartOrientationListener
import org.jellyfin.mobile.utils.isWebViewSupported
import org.jellyfin.mobile.utils.replaceFragment
import org.jellyfin.mobile.viewmodel.MainViewModel
import org.jellyfin.mobile.viewmodel.ServerState
Expand Down Expand Up @@ -53,6 +55,19 @@ class MainActivity : AppCompatActivity() {
// Bind player service
bindService(Intent(this, RemotePlayerService::class.java), serviceConnection, Service.BIND_AUTO_CREATE)

// Check WebView support
if (!isWebViewSupported()) {
AlertDialog.Builder(this).apply {
setTitle(R.string.dialog_web_view_not_supported)
setMessage(R.string.dialog_web_view_not_supported_message)
setCancelable(false)
setNegativeButton(R.string.dialog_button_close_app) { _, _ ->
finish()
}
}.show()
return
}

// Load UI
lifecycleScope.launch {
mainViewModel.serverState.collect { state ->
Expand Down
43 changes: 37 additions & 6 deletions app/src/main/java/org/jellyfin/mobile/fragment/WebViewFragment.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.jellyfin.mobile.fragment

import android.annotation.SuppressLint
import android.content.Intent
import android.graphics.Rect
import android.net.Uri
Expand All @@ -14,6 +13,7 @@ import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse
import android.webkit.WebView
import androidx.activity.addCallback
import androidx.appcompat.app.AlertDialog
import androidx.core.view.ViewCompat
import androidx.core.view.doOnNextLayout
import androidx.fragment.app.Fragment
Expand All @@ -22,6 +22,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.webkit.WebResourceErrorCompat
import androidx.webkit.WebViewClientCompat
import androidx.webkit.WebViewCompat
import androidx.webkit.WebViewFeature
import kotlinx.coroutines.launch
import org.jellyfin.apiclient.interaction.ApiClient
Expand Down Expand Up @@ -126,8 +127,41 @@ class WebViewFragment : Fragment(), NativePlayerHost {
_webViewBinding = null
}

@SuppressLint("SetJavaScriptEnabled")
private fun WebView.initialize() {
if (isOutdated()) { // Check WebView version
AlertDialog.Builder(requireContext()).apply {
setTitle(R.string.dialog_web_view_outdated)
setMessage(R.string.dialog_web_view_outdated_message)
setCancelable(false)

val webViewPackage = WebViewCompat.getCurrentWebViewPackage(context)
if (webViewPackage != null) {
setPositiveButton(R.string.dialog_button_check_for_updates) { _, _ ->
val marketUri = Uri.Builder().apply {
scheme("market")
authority("details")
appendQueryParameter("id", webViewPackage.packageName)
}.build()
val referrerUri = Uri.Builder().apply {
scheme("android-app")
authority(context.packageName)
}.build()

val marketIntent = Intent(Intent.ACTION_VIEW).apply {
data = marketUri
putExtra(Intent.EXTRA_REFERRER, referrerUri)
}
startActivity(marketIntent)
requireActivity().finish()
}
}
setNegativeButton(R.string.dialog_button_close_app) { _, _ ->
requireActivity().finish()
}
}.show()
return
}

webViewClient = object : WebViewClientCompat() {
override fun shouldInterceptRequest(webView: WebView, request: WebResourceRequest): WebResourceResponse? {
val url = request.url
Expand Down Expand Up @@ -186,10 +220,7 @@ class WebViewFragment : Fragment(), NativePlayerHost {
}
}
webChromeClient = WebChromeClient()
settings.apply {
javaScriptEnabled = true
domStorageEnabled = true
}
settings.applyDefault()
addJavascriptInterface(NativeInterface(this@WebViewFragment), "NativeInterface")
addJavascriptInterface(NativePlayer(this@WebViewFragment), "NativePlayer")
addJavascriptInterface(externalPlayer, "ExternalPlayer")
Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/org/jellyfin/mobile/utils/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ object Constants {
const val APP_INFO_VERSION: String = BuildConfig.VERSION_NAME

// Webapp constants
const val MINIMUM_WEB_VIEW_VERSION = 88
const val WEB_CONFIG_PATH = "config.json"
const val CAST_SDK_PATH = "cast_sender.js"
const val SESSION_CAPABILITIES_PATH = "sessions/capabilities/full"
Expand Down
68 changes: 68 additions & 0 deletions app/src/main/java/org/jellyfin/mobile/utils/WebViewUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* Taken and adapted from https://github.com/tachiyomiorg/tachiyomi/blob/master/app/src/main/java/eu/kanade/tachiyomi/util/system/WebViewUtil.kt
*
* Copyright 2015 Javier Tomás
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jellyfin.mobile.utils

import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.webkit.CookieManager
import android.webkit.WebSettings
import android.webkit.WebView
import timber.log.Timber

fun Context.isWebViewSupported(): Boolean {
try {
// May throw android.webkit.WebViewFactory$MissingWebViewPackageException if WebView
// is not installed
CookieManager.getInstance()
} catch (e: Throwable) {
Timber.e(e)
return false
}

return packageManager.hasSystemFeature(PackageManager.FEATURE_WEBVIEW)
}

fun WebView.isOutdated(): Boolean =
getWebViewMajorVersion() < Constants.MINIMUM_WEB_VIEW_VERSION

private fun WebView.getWebViewMajorVersion(): Int {
return """.*Chrome/(\d+)\..*""".toRegex().matchEntire(getDefaultUserAgentString())?.let { match ->
match.groupValues.getOrNull(1)?.toInt()
} ?: 0
}

// Based on https://stackoverflow.com/a/29218966
private fun WebView.getDefaultUserAgentString(): String {
val originalUA: String = settings.userAgentString

// Next call to getUserAgentString() will get us the default
settings.userAgentString = null
val defaultUserAgentString = settings.userAgentString

// Revert to original UA string
settings.userAgentString = originalUA

return defaultUserAgentString
}

@SuppressLint("SetJavaScriptEnabled")
fun WebSettings.applyDefault() {
javaScriptEnabled = true
domStorageEnabled = true
}
7 changes: 7 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
<string name="dialog_web_view_not_supported">WebView not supported</string>
<string name="dialog_web_view_not_supported_message">WebViews are not supported by this device.\nInstall a compatible WebView implementation or switch to a supported device.</string>
<string name="dialog_web_view_outdated">WebView outdated</string>
<string name="dialog_web_view_outdated_message">The WebView implementation used by your device is outdated.\nPlease update it to a compatible version.</string>
<string name="dialog_button_check_for_updates">Check for updates</string>
<string name="dialog_button_close_app">Close app</string>

<string name="connect_to_server_title">Connect to Server</string>
<string name="host_input_hint">Host</string>
<string name="connection_error_invalid_format">Host has an invalid format</string>
Expand Down

0 comments on commit 5f73db5

Please sign in to comment.