Merge pull request #326 from jellyfin/webview-checks

Check WebView support and version
This commit is contained in:
Niels van Velzen
2021-03-23 19:55:53 +01:00
committed by GitHub
6 changed files with 131 additions and 7 deletions
@@ -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
@@ -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
@@ -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 ->
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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")
@@ -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"
@@ -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
View File
@@ -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>
@@ -579,7 +579,9 @@ public final class Chromecast implements IChromecast {
stopRouteScan(callback);
sessionStop(callback);
media.destroy();
if (media != null) {
media.destroy();
}
media = null;
if (connection != null) {
connection.destroy();