Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check WebView support and version #326

Merged
merged 2 commits into from
Mar 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Maxr1998 marked this conversation as resolved.
Show resolved Hide resolved
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
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,9 @@ protected void callback(boolean keep, @Nullable String err, @Nullable String res

stopRouteScan(callback);
sessionStop(callback);
media.destroy();
if (media != null) {
media.destroy();
}
media = null;
if (connection != null) {
connection.destroy();
Expand Down