WebView doesn't load iFrame video due to security policy violation - android

I am trying to load an iFrame in my WebView.
The video/iFrame doesn't load reliably
This is what happens :
When I load the screen, in the webview's place there is blank space, but eventually the video might load, but most times, it doesn't load.
However, if I lock the screen and unlock it, the video loads just fine.
I extended the ChromiumWebClient and logged the following methods :
onProgressChanged(WebView view, int newProgress) {}
onConsoleMessage(ConsoleMessage consoleMessage) {}
Turns out the video downloads 80% then, incurs the security exception, but it does seem to continue downloading in a short while and downloads 100%.
Despite downloading 100%, the webview doesn't show the video(player).
However, as mentioned above, if I lock and unlock the screen, (the now downloaded) video shows up correctly.
iFrame(could be from any source, not just vimeo) :
"https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fplayer.vimeo.com%2Fvideo%2F215269493&url=https%3A%2F%2Fvimeo.com%2F215269493&image=https%3A%2F%2Fi.vimeocdn.com%2Fvideo%2F632188996_1280.jpg&key=0a5d85b22c79478aa887a7e8061bef56&type=text%2Fhtml&schema=vimeo' width='525' height='295' scrolling='no' frameborder='0' allowfullscreen>"
Web view code :
fun onResume() {
videoWebView.onResume()
}
fun onPause() {
videoWebView.onPause()
}
private fun loadVideo(videoWebView: WebView, iFrame: String) {
videoWebView.setWebViewClient(WebViewClient())
videoWebView.setWebChromeClient(WebChromeClient())
videoWebView.settings.javaScriptEnabled = true
videoWebView.settings.domStorageEnabled = true
videoWebView.settings.loadsImagesAutomatically = true
videoWebView.settings.loadWithOverviewMode = true
videoWebView.loadData(
iFrame,
WebFormatUtil.MIMETYPE_HTML,
WebFormatUtil.ENCODING_UTF8)
}
I do have hardwareAcceleration and internet permission enabled in manifest
The Logs throw the following (2 messages):
I/chromium: [INFO:CONSOLE(11)] "The deviceorientation event is deprecated on insecure origins, and support will be removed in the future. You should consider switching your application to a secure origin, such as HTTPS. See https://shortened url for more details.", source: https://f.vimeocdn.com/p/2.69.8/js/player.js (11)
I/chromium: [INFO:CONSOLE(0)] "Refused to load the image 'android-webview-video-poster:default_video_poster/-1316638038331145773' because it violates the following Content Security Policy directive: "img-src 'self' data: https://i.vimeocdn.com https://secure-b.vimeocdn.com https://f.vimeocdn.com https://vimeo.com https://secure.gravatar.com https://i0.wp.com https://i1.wp.com https://i2.wp.com https://player.vimeo.com https://*.ci.vimeows.com https://f.vimeocdn.com".
", source: https://player.vimeo.com/video/215269493 (0)
This turns out to be a known issue in the Chromium client and has not ben fixed yet.
What I am looking for :
I am looking for a way such that when I open the screen the video is shown, without having to turn on and off the screen.
Not sure if anything other than a hack around is possible.
Note : I have tried a lot of other answers, most of the deal with other issues such as config and so on.
I have isolated out the issue to be in ChromiumClient used for Android WebView, which those other answers are not about.

This is how I fixed my issue :
private fun loadVideo(videoWebView: WebView, iFrame: String) {
webView.setWebChromeClient(object: WebChromeClient() {
override fun onConsoleMessage(consoleMessage: ConsoleMessage): Boolean {
if (consoleMessage.messageLevel() == ConsoleMessage.MessageLevel.ERROR) {
webView.onResume()
}
return super.onConsoleMessage(consoleMessage)
}
})
//......rest of the method like before
}
Every time after lock/unlocking the screen, the video showed up fine.
What that means is that the onPause() and onResume() were fired inside the webView.
Additionally, Every time the security exception is incurred the onConsoleMessage(consoleMessage: ConsoleMessage) in Chromium client is fired with the tag ERROR.
This is a hack around, which so far has been safe, from my testing.
Until the actual issue is fixed, this works for me.

Related

Android WebView with AngularJS application route changes not being detected in shouldOverrideUrlLoading

I have been working on a hybrid android application. Currently a WebView in our application is pointing to an AngularJS 1.5.7 application. When the user hits a button inside of the application that changes the route I was expecting the shouldOverrideUrlLoading function to be called inside of my WebViewClient. However, this is not the case. It looks like shouldOverrideUrlLoading does not get hit on Angualar route changes.
This being the case I have gone down the following rabbit holes:
onPageFinished - Overriding this function in the WebViewClient works, however, it is not being called until after the new route is getting hit. Which is adding to the application loading time and creating a choppy experience. ` #Override
public void onPageFinished(WebView view, String url) {
if (url.endsWith("/#/")) {
signOut();
} else if (url.endsWith("/login")) {
// TODO: show some sort of failure message?
Log.i("Login Route", "The webview just attempted to go to the login route.");
signOut();
} else if (url.endsWith("/security")) {
Intent intent = new Intent(getApplicationContext(), SecurityActivity.class);
startActivity(intent);
}
}`
shouldInterceptRequest - Overriding this function allows you to watch for requests. However, by the time the requests go out from the AngularJS application the web view is showing a new route once again providing a choppy user experience.
onLoadResource - same
JavaScriptInterface - Currently I have set up a JavaScript interface to watch for window.location changes. This seems to catch the route changes quicker than any of the above options, however, there is still a glimpse quick flicker of the web page I do not want to do go to. You can find how to do Javascript bridging on this post
Any suggestions would be greatly appreciated! Thanks.

Android Webview: Scripts may close only the windows that were opened by it

I am loading a particular url
For eg.
webview.loadUrl("some.domain.com")
Afterwords I am redirecting it to some other domain and then coming back to my domain. Then I try to close the window in my javascript (window.close()). I get the below error while debugging remotely on chrome developer tools
Scripts may close only the windows that were opened by it.
I am getting the above error even though I am on the same domain with which I opened it.
Any help would be very much appreciated. Thank you.
This answer will be from Android App Developer perspective.
I hope it will help someone.
The problem was very similar for me: I was opening a web site via webview, and some of the links were opening in a new window. The thing is that webview cannot work with web windows out of the box. I mean, it can be possible, but not as expected (in my case, when a link was opened in a separate window from javascript perspective, it were overriding previously opened page, and cannot be closed with a window.close() from javascript, which eventually were causing a state loss on a previous page).
So the task in my case was to open a single link in a window and go back to previous page without any state loss. That was my solution.
I had two separate WebViews - one as a main one, and one for links in window.
To be able to react on a "link in new window" event, I'll configured main webView with this code:
webView.settings.javaScriptEnabled = true
webView.settings.javaScriptCanOpenWindowsAutomatically = true
webView.settings.setSupportMultipleWindows(true)
webView.webChromeClient = object : WebChromeClient() {
override fun onCreateWindow(view: WebView?, isDialog: Boolean, isUserGesture: Boolean,
resultMsg: Message?): Boolean {
handleCreateWebWindowRequest(resultMsg)
return true
}
}
We need only onCreateWindow callback to override in main webView chrome client, since it will only open new windows. And also allow a multiwindow support in webView.settings. When an onCreateWindow callback triggers, do the following:
#SuppressLint("SetJavaScriptEnabled")
override fun handleCreateWebWindowRequest(resultMsg: Message?) {
if (resultMsg == null) return
if (resultMsg.obj != null && resultMsg.obj is WebView.WebViewTransport)
{
val transport = resultMsg.obj as WebView.WebViewTransport
windowWebView = WebView(this)
windowWebView?.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT)
windowWebView?.settings?.javaScriptEnabled = true
windowWebView?.settings?.javaScriptCanOpenWindowsAutomatically = true
windowWebView?.settings?.setSupportMultipleWindows(true)
windowWebView?.webViewClient = WebViewClient()
windowWebView?.webChromeClient = object : WebChromeClient() {
override fun onCloseWindow(window: WebView?) {
super.onCloseWindow(window)
handleCloseWebWindowRequest()
}
}
container.addView(windowWebView)
webView.visibility = View.GONE
transport.webView = windowWebView
resultMsg.sendToTarget()
}
}
Basically we're sending this (create window) request to a separate webView. In it we should also allow a multiwindow support and attach a chrome client, in wich we should listen only onCloseWindow event, since this webView should behave as a window. When onCloseWindow triggers, we're just closing (hiding/removing) webView that should act as a window, and returning to the main one. Here isWebWindowOpened method call just checks if the windowWebView is not null and visible.
private fun handleCloseWebWindowRequest() {
if (!isWebWindowOpened()) return
container.removeView(windowWebView)
webView.visibility = View.VISIBLE
windowWebView = null
}
The only thing I can mention, is that when a windowWebView is opened, onBackPressed action should close it calling handleCloseWebWindowRequest.

android – How to check if webview is available?

Is there a way to check if webview is available on the device?
Background:
If I add <uses-feature android:name="android.software.webview"/> to the Manifest the number of supported devices on Google Play drops from over 12,000 to less than 6,000 devices. So I added android:required="false" to this feature. In case webview is available websites should be displayed inside the app otherwise launched in the default browser:
String mUrl = "http://www.example.com";
if (*** WHAT TO PUT HERE? ***) {
WebView webview = (WebView) findViewById(R.id.webView);
if (Build.VERSION.SDK_INT >= 24) {
webview.setWebViewClient(new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
view.loadUrl(request.toString());
return false;
}
});
} else {
webview.setWebViewClient(new WebViewClient() {
#SuppressWarnings("deprecation")
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return false;
}
});
}
webview.loadUrl(mUrl);
} else {
Uri uri = Uri.parse(mUrl);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
}
Edit (to make things clear): <uses-permission android:name="android.permission.INTERNET" /> is (and always had been) part of the manifest. It’s just the addition of <uses-feature android:name="android.software.webview" /> which causes the drop of supported devices.
There is someone having the same issue here: https://issuetracker.google.com/issues/37035282
(unfortunately not answered)
Although #vsatkh pointed out that it is not necessarily needed to declare this feature as required, you can check the device’s feature compatibility as follows:
getPackageManager().hasSystemFeature("android.software.webview")
This method returns true or false.
Some additional information about Google Play’s filtering:
Google Play only filters supported devices based on <uses-feature> elements declared in the manifest. <uses-permission> elements don’t affect Google Play’s filtering unless they imply a feature. android.permission.INTERNET does not imply any feature. Permissions that imply features are listed here:
https://developer.android.com/guide/topics/manifest/uses-feature-element.html#permissions
Alternative solution:
val hasWebView: Boolean = kotlin.runCatching { CookieManager.getInstance() }.isSuccess
It works because if WebView is not available then CookieManager.getInstance() will throw AndroidRuntimeException.
getPackageManager().hasSystemFeature("android.software.webview") is not reliable. It will return true if the device is supposed to have a webview, but has disabled it in system settings.
I tested it on a Huawei P20 by going to Settings -> Apps -> Android System WebView -> Disable
There is a way around: CookieManager
AFAIK: There is no dedicated API that gives you that information. But if I told you there is a way around.
As Torkel mentioned, getPackageManager().hasSystemFeature(..) does not guarantee to tell the Android System WebView is enabled or disabled.
getPackageManager().hasSystemFeature(..) will still return true even a user disables the WebView in Settings.
CookieManager
So an alternative would be to use CookieManager.
An application WebView cookie is managed by CookieManager. It throws an exception if you try to create CookieManager instance when the WebView is disabled.
In Kotlin, you can get result from block statements but it is not possible in Java. So you can use Exception Handling logic in a function to decide.
Here is an example
public boolean webViewEnabled(){
try{
CookieManager.getInstance();
return true;
}catch(Exception e){
return false;
}
}
Note:
The current implementation might change in the future, or they might come up with a dedicated API for this. I have tried finding a perfect solution in many forums but this is the closest I have reached.
This solution will prevent your app from crashing but you will still see the error in your logs because CookieManager.getInstance throws an Exception object.
You can read more detail here https://source.android.com/compatibility/android-cdd.pdf.
According to the section WebView Compatibility, android.software.webview feature is indicate that device or oem must provides a complete implementation of the android.webkit.WebView API. So the number of device on play store was drop because actually, most device did not fully implemented all api required by webkit.
So if your web content required certain html5 feature please check here http://mobilehtml5.org/ if your app target kitkat or even lollipop+ then you should be safe.
So there is no need to declare android.software.webview feature unless your web content really need all api of webkit.
As of API 26, you can use WebView.getCurrentWebViewPackage() to check if there is a valid WebView installed and active. The CookieManager check can be really slow (upwards of 500ms) and can impact startup performance if the check is happening in the critical path.
From the documentation:
If WebView has already been loaded into the current process this method will return the package that was used to load it. Otherwise, the package that would be used if the WebView was loaded right now will be returned; this does not cause WebView to be loaded, so this information may become outdated at any time.
The WebView package changes either when the current WebView package is updated, disabled, or uninstalled. It can also be changed through a Developer Setting. If the WebView package changes, any app process that has loaded WebView will be killed. The next time the app starts and loads WebView it will use the new WebView package instead.
#return the current WebView package, or {#code null} if there is none.
You can verify that this is working as expected by swapping the WebView implementation and then calling this method to list the PackageName (e.g if you're running the dev/beta Chrome WebViews) by calling:
WebView.getCurrentWebViewPackage()?.packageName
You can test disabled state by running the following ADB command (although you might need an emulator running as root via adb root): adb shell pm disable <webview-package-id>.
WebView has been had in Android API 1 so it's always available what's not it's the internet connection
https://developer.android.com/reference/android/webkit/WebView.html
In you manifest you need internet permission :
<uses-permission android:name="android.permission.INTERNET" />

Android 4.4 giving ERR_CACHE_MISS error in onReceivedError for WebView back

I have a webview in my Layout. By default, a search form is opened in it. On search, a listing section appears below the search form. If any link in the list is clicked, the details page opened. Now I want to controlled the back navigation for the webview. I placed this code in Activity.
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
Log.d("TYPE", TYPE);
WebView myWebView = null;
if (TYPE.equalsIgnoreCase("REPORT_ACTIVITY"))
myWebView = reportView;
if (TYPE.equalsIgnoreCase("FEEDBACK_ACTIVITY"))
myWebView = feedbackView;
if (myWebView != null)
// Check if the key event was the Back button and if there's history
if ((keyCode == KeyEvent.KEYCODE_BACK) && myWebView.canGoBack()) {
myWebView.goBack();
return true;
}
// If it wasn't the Back key or there's no web page history, bubble up
// to the default
// system behavior (probably exit the activity)
return super.onKeyDown(keyCode, event);
}
private WebViewClient webViewClient = new WebViewClient() {
public void onPageStarted(WebView view, String url, Bitmap favicon) {
Log.d("onPageStarted", "onPageStarted");
loadProgressBarBox.setVisibility(View.VISIBLE);
//view.setVisibility(View.GONE);
}
public void onPageFinished(WebView view, String url) {
Log.d("onPageFinished", "onPageFinished");
loadProgressBarBox.setVisibility(View.GONE);
}
public void onReceivedError(WebView view, int errorCode,
String description, String failingUrl) {
Log.d("Error", "Error code: " + errorCode + "/" + description);
}
}
I have also set a WebViewClient with the WebView. When I going back using back button it is working fine for any version 4.4. But when I am trying in Android 4.4, it is coming back fine from details page to listing page. But as soon as I am trying to go back again, its throwing error code -1 and ERR_CACHE_MISS in description. No page is displayed.
09-04 06:59:05.666: D/Error(1102): Error code: -1/net::ERR_CACHE_MISS
How to solve this problem in Android 4.4?
This error actually stems from outside of your application in most cases (occasionally it's just a missing INTERNET permission, but that doesn't sound like the case here).
I was typing out an explanation, but found a much more straightforward example that doubles as an explanation in this answer to another question. Here's the relevant bits, re-hashed a little:
Joe fills in an order form with his credit card information
The server processes that information and returns a confirmation/receipt page that's marked with no-cache in the header, meaning it will always be requested from the server.
Joe goes to another page.
Joe clicks back because he wants to double check something, taking him to the confirmation page.
The problem arises from that last step. The confirmation page was marked with no-cache, so it has to be requested from the server again. But to show the same page correctly, the same data that was passed the first time needs to get sent again.
This results in Joe getting billed twice, since a new request is being made with the same information as last time. Joe will not be a happy camper when he finds two charges on his account and an extra pair of tents on his doorstep.
It seems this situation was common enough that it is now a standard error across most browsers, and apparently, newer versions of Android. The error actually originates from Chromium, which is why you'll see the same error in Google Chrome, and why you only see it in 4.4 (which introduced a new version of the WebView based on Chromium).
In fact, you have actually probably seen it before, it's the message that shows up in most browsers warning you with something along the lines of "To refresh this page, the browser will have to resend data...yada yada yada".
This is Android 4.4's way warning you of what's going on. How to fix it really depends on what you're connecting to, but if you search for this situation, you'll find that it's fairly common, and has fixes. The exact trigger of the error is actually when the request can't be serviced from cache (in this case, no-cache is causing that).
Depending on the nature of the request, maybe no-cache isn't actually needed.
But from your application's perspective, the main problem is, onReceiveError is a sort of "last resort" for the WebView. Errors you get there have propagated from underlying system. And once you end up there, you can't continue the page load as it stands. So you don't have a chance to allow that resend, and you can't give the user that option, unlike, say Google Chrome does.
I ran into the same issue because in my manifest folder I had the Internet permission capitalized:
I had (error)
<uses-permission android:name="ANDROID.PERMISSION.INTERNET"/>
Should have (no error)
<uses-permission android:name="android.permission.INTERNET"/>
Use
if (Build.VERSION.SDK_INT >= 19) {
mWebView.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
}
It will fix ERR_CACHE_MISS in the WebView.
Maybe you will need to change it to SDK_INT == 19 after some Lollipop WebView updates, but it works for now.
this permission in your andriodManifest.xml file
<uses-permission android:name="android.permission.INTERNET"/>

mediaelement.js - android default browser and dolphin browser loading mp3 issues

Good day,
I'm currently building a website for myself (I'm a composer/producer), and I'm using Media Element for the main demo page. The page is under construction at the following link:
http://www.vincentrubinetti.com/listen.html
http://www.vincentrubinetti.com/listen.js
Below are what I think are the relevant functions and code:
function initPlayer()
{
player = new MediaElementPlayer('#listen_player',{
success: function (mediaElement, domObject)
{
mediaElement.addEventListener('play', resumeSong, false);
mediaElement.addEventListener('ended', playNextSong, false);
mediaElement.addEventListener('pause', pauseSong, false);
}
});
[omitted]
}
function setSong(element)
{
if (element != "")
{
unselectAllSongs();
document.getElementById(element).className = "listen_song_highlight";
[omitted]
var newSrc = document.querySelector("#"+element+" .listen_song_source").title;
player.pause();
player.setSrc(newSrc);
player.load();
}
}
function playSong(element)
{
document.querySelector("#"+element+" .listen_song_status").innerHTML = "playing";
player.play();
}
function playNextSong()
{
var newSong = document.querySelector(".listen_song_highlight + div.listen_song");
if (autoplay && newSong != null)
{
setSong(newSong.id);
playSong(newSong.id);
}
else
{
document.querySelector(".listen_song_highlight .listen_song_status").innerHTML = "stopped";
}
}
All the CSS end of this seems to be fine. It finds the next song on the list and sets the new source; I've verified it does this properly via alert and other debugging. However, on the Android default browser and the Dolphin browser, it seems to not be able to load the mp3 sometimes, and prematurely triggers the "ended" event to go to the next song. The result is that it appears to skip 2-3 songs after one is done playing. And it takes some finagling even to get it to play one, clicking on the song divs and the player play button. I can type in the same url that's in the html, and the browser will play/download it just fine, no problems accessing or loading it.
Here are the 3 mp3 files that repeat down the playlist, for reference. They are by me, but are placeholders for the real music.
NEW/music/creation.mp3
NEW/music/startup.mp3
NEW/music/win.mp3
Note that all of this works properly on Chrome, Firefox, IE8+, and Android Chrome (I haven't tested iPhone or iPad yet).
Is my diagnosis correct? Can anyone point me in the right direction? Is there any experience where MediaElement doesn't work properly on the Android default and Dolphin browsers?
I have the same problem in android (but with video...)
I was told that this is an issue with the file not being downloaded in time -- i.e. the browser starts the download, but since the file isn't downloaded it is 0:00 long, so the ended event is triggered.
If there is someway to make it so that the video / audio can be downloaded faster it solves the problem.
Strangely enough, if the device has good WiFi then this problem basically disappears.
See what John Dyer said about it here:
https://github.com/johndyer/mediaelement/issues/543

Categories

Resources