HTML link to local file in WebView with target API 24 - android

How can one use HTML links to navigate to local files (HTML pages) in WebView if targeting API 24 or higher?
This has been discussed before and solutions use the file:// URI scheme.
What worked so far was using
Go to local page
in an HTML file that is displayed in a WebView and clicking the link would load the local page app/src/main/assets/my_page.html.
However, starting from API 24, a FileUriExposedException is raised when clicking such a link. From logcat:
mypackage.myapp W/System.err: android.os.FileUriExposedException: file:///android_asset/my_page.html exposed beyond app through Intent.getData()
...
mypackage.myapp W/System.err: at org.chromium.android_webview.ResourcesContextWrapperFactory$WebViewContextWrapper.startActivity(ResourcesContextWrapperFactory.java:121)
mypackage.myapp W/System.err: at org.chromium.android_webview.AwContentsClient.sendBrowsingIntent(AwContentsClient.java:203)
According to the documentation, this is thrown when "an application exposes a file:// Uri to another app.". I wonder why this is the case, because according to the log everything seems to happen inside mypackage.myapp.
The documentation suggests using the content:// URI scheme instead, but this does not work in HTML files.

The following workaround (based on this answer) intercepts the loading of a file:// URI in the WebView and then loads it directly by app code with WebView.loadUrl(...). This is possible by overriding WebView.shouldOverrideUrlLoading in a WebViewClient passed to the WebView, e.g. when initializing it.
As there was an API change for this method in API 24, for compatibility there are two versions in the code (technically in the API<24 case one could also do as before, letting WebView open the file:// URI because the exception is not raised on devices running API<24).
if (android.os.Build.VERSION.SDK_INT >= 24) {
webView.setWebViewClient(new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(WebView webView, WebResourceRequest webResourceRequest) {
if (webResourceRequest.getUrl().getScheme().equals("file")) {
webView.loadUrl(webResourceRequest.getUrl().toString());
} else {
// If the URI is not pointing to a local file, open with an ACTION_VIEW Intent
webView.getContext().startActivity(new Intent(Intent.ACTION_VIEW, webResourceRequest.getUrl()));
}
return true; // in both cases we handle the link manually
}
});
} else {
webView.setWebViewClient(new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(WebView webView, String url) {
if (Uri.parse(url).getScheme().equals("file")) {
webView.loadUrl(url);
} else {
// If the URI is not pointing to a local file, open with an ACTION_VIEW Intent
webView.getContext().startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
}
return true; // in both cases we handle the link manually
}
});
}
The reason why there is an exception when letting the WebView open the link must have something to do with the Intent created by the WebView but I don't see whether or how it is exposed to another app.
That the workaround works is then because the WebView does not do anything with the link (no Intent is created), instead, when the link is clicked, the app gets control and opens the file:// URI direclty by passing it to WebView.loadUrl(...) - which seems to be fine.
I assume (but do not claim) that regarding security this is fine because the URI is only used to load the file it points to in this single WebView (and if this was problematic the system should throw the FileUriExposedException).

I never link in the HTML that way if you need to load an other page:
Go to local page
I link this way because my map struture look like this:
Go to local page
You just need that methode in your MainActivity.java and that will work:
private class MyBrowser extends WebViewClient {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.startsWith("tel:") || url.startsWith("sms:") || url.startsWith("smsto:") || url.startsWith("mailto:") || url.startsWith("mms:") || url.startsWith("mmsto:") || url.startsWith("market:")) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(intent);
return true;
}
else {
view.loadUrl(url);
return true;
}
}
}
If you have any questions or It still don't work let me know

Related

webview host application in android

I am a beginner. When I start to make an app with webview.
I saw on docs that https://developer.android.com/reference/android/webkit/WebViewClient#shouldOverrideUrlLoading(android.webkit.WebView,%20java.lang.String)
Give the host application a chance to take control when a URL is about to be loaded in the current WebView.
I am weak in Englis but I know what is hosting. but I don't get what is host application, why it calls like that?
1) Is host application means a web browser or webview in my app?
2) It should be helpful how shouldoverrideurlloading works with webview and browser.
3) return true will open a web browser??
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
final Uri uri = Uri.parse(url);
return handleUri(view, uri);
}
#TargetApi(Build.VERSION_CODES.N)
#Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
final Uri uri = request.getUrl();
return handleUri(view, uri);
}
and handleUri method
private boolean handleUri(WebView view, Uri uri) {
final String scheme = uri.getScheme();
final String host = uri.getHost();
// Based on some condition you need to determine if you are going to load the url
// in your web view itself or in a browser.
// You can use `host` or `scheme` or any part of the `uri` to decide.
if (scheme.startsWith("http:") || scheme.startsWith("https:")) {
view.loadUrl(uri.getPath());
return true;
} else {
return false;
}
}
When using webview you use shouldOverrideUrlLoading to enter a specific url(the one you intend the user to see). This method also will, with some more advanced coding used when scrapping data from web pages, allow you to collect the html data and possible modify or utilize the html document code in creative ways. Then displaying the webview to the user when your ready.
Ps. Heads up depending on which api your minimum is set at you have to use it differently. There are some tutorials on google searches but most are outdated. This is not a common practice. More often than not apps use an api provided by a url and then display the data or intended visuals.

Display a pdf with dynamic generating url in WebView / Google Docs

so I just started with Android programming and I am trying to make a little app using WebView. There is a url that redirects you to a pdf, I know WebView does not render pdf. So I want to use intent and display the pdf in Google Docs. However, the pdf address is randomly generated so I cant link it with
WebView.loadUrl("http://docs.google.com/gview?embedded=true&url=" + pdfURL);
How can I send an intent to Google Docs without using the exact pdf address?
I don't know what "randomly generated" means.
But the first thing that comes to my mind is to set a WebViewClient and override shouldOverrideUrlLoading:
webView.setWebViewClient(new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.endsWith(".pdf") == true) {
view.loadUrl("http://docs.google.com/gview?embedded=true&url=" + url);
return true;
}
return false;
}
});
Some more info in this thread.

Open Soundcloud URL in native Soundcloud app from WebView

Scenario
I have a WebView in my Android app which contains a Soundcloud embed (from Embedly). This embed has two buttons: "Play on Soundcloud" and "Listen in browser".
The "Play on Soundcloud" button contains a URL in format intent://tracks:257659076#Intent;scheme=soundcloud;package=com.soundcloud.android;end
Code
My WebView uses a custom WebViewClient (because I need to intercept some URLs for some different stuff).
protected class WebViewClient extends android.webkit.WebViewClient {
public WebViewClient() { }
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
PackageManager packageManager = context.getPackageManager();
// Create an Intent from the URL.
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
// Find out if I have any activities which will handle the URL.
List<ResolveInfo> resolveInfoList = packageManager.queryIntentActivities(intent, 0);
// If we have an app installed that can handle the URL, then use it.
if (resolveInfoList != null && resolveInfoList.size() > 0) {
Intent viewUrlIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
context.startActivity(viewUrlIntent);
}
else {
// Do something else.
}
return true;
}
}
Problem
Clicking "Listen in browser" plays the track in the embed itself and works fine. Clicking "Play on Soundcloud" will call into shouldOverrideUrlLoading in the WebViewClient above (as expected). However, my code to find a activity can't find anything that can deal with this Soundcloud URL.
If I don't set my WebViewClient on the WebView (so it just does its own thing), the "Play on Soundcloud" button will work as expected and launch the Soundcloud app.
Temporary (crap) solution
I've managed to make this do what I want it to do by parsing the URL to get the track ID, then building a new URL using a format that Soundcloud definitely accepts (thanks to this SO post). A URL in the format "soundcloud://tracks:[TRACK_ID]" will be accepted by the Soundcloud app.
But WHY?
Either I am doing the whole "find out what activities can handle this URL" thing wrong, or maybe(?!) the default WebViewClient used by the WebView handles this explicitly?! Seems implausible.
I'm just extending the Temporary (crap) solution here, so this is far from a perfect answer, but might still help someone who absolutely needs to get this to work, also with private tracks.
The replace method works if the track is public, but with private tracks this does not work, probably because of the missing secret token in the intent URL.
Unfortunately the embed player does not contain all the necessary pieces of the URL I need to generate, except inside the iframe, which I cannot access due to cross-origin policy. So in addition to the iframe code I also need the share link.
What I ended up doing is making sure that the containing HTML page has the share link as a JS variable. I then read that variable using Java and create a new Intent with that URL. This works, because the official app also registers all soundcloud.com URLs.
So for private tracks this goes to the HTML page:
<script>var soundCloudURL = "https://soundcloud.com/my-profile/my-track/my-secret-token";</script>
Then inside your Android app you would have something like this:
#Override
public boolean shouldOverrideUrlLoading (WebView view, String url) {
if (uri.getScheme().contains("intent")) {
openSoundCloudPlayer();
return true;
}
}
private void openSoundCloudPlayer() {
appWebView.evaluateJavascript("(function() { return soundCloudUrl })();", new ValueCallback<String>() {
#Override
public void onReceiveValue(String soundCloudUrl) {
// JS null is converted into a string "null", not Java null.
if (soundCloudUrl != "null") {
// Take out the quotes from the string
soundCloudUrl = soundCloudUrl.replace("\"", "");
Uri newUri = Uri.parse(soundCloudUrl);
Intent intent = new Intent(Intent.ACTION_VIEW, newUri);
startActivity(intent);
}
}
});
}

shouldOverrideUrlLoading loading internally and externally on SDK versions < 18

I'm using a standard WebView implementation, and overriding the shouldOverrideUrlLoading method to catch request to external domains. The call is being captured on all of my tested versions (15-22); however,on 15-18, the WebView navigates to the requested URL before shouldOverrideUrlLoading is called to execute the External Browser request.
Example:
SDK >= 19
WebView -> Load URL -> shouldOverrideUrlLoading(TRUE) -> URL loaded in External Browser and WebView's state is retained.
SDK <= 18
WebView -> Load URL -> URL loaded in WebView -> shouldOverrideUrlLoading(TRUE) -> URL loaded in External Browser and WebView's state is lost.
WebView Override Code:
private void webViewClient() {
webView.setWebViewClient(new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.startsWith(BASE_URL)) {
return false;
} else {
Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(i);
return true;
}
}
});
}
One possible cause:
This behavior makes sense if the URL being loaded is "invalid" (like something other than "http://whatever.com/"), and is somehow also a "redirect".
If the invalid url being loaded is a "redirect"... >=19 will not call the shouldOverrideUrlLoading at all.
If the FINAL URL is valid, however, and doesn't start with BASE_URL, it would call shouldOverrideUrlLoading, then launch the new window, as your code says.
That said, I have no idea how you would get an invalid URL to be a redirect -- so without more information about the URLs (BASE_URL and the URL being requested), it's impossible to say.
Read more about the differences between the WebView in 19+... big changes were made at that time:
https://developer.android.com/guide/webapps/migrating.html

getHitTestResult().getType() on an iframe in a WebView always returns 0

I'm trying to override the behavior when the user taps an iframe link in a WebView (a DroidGap WebView, to be precise) in order to have that link open up in Android's browser. I believe code like this ought to be sufficient to achieve that:
public void onLoadResource (WebView view, String url) {
if (url.contains("foo")) {
if(view.getHitTestResult().getType() > 0){
view.getContext().startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
view.stopLoading();
}
}
}
However, whenever I tap anywhere in the iframe, the type HitTestResult type is always 0 (i.e. unknown). Any idea why this might be?

Categories

Resources