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);
}
}
});
}
Related
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.
FB share button could successfully oepn the FB messenger app in Chrome browser.
However, the same source code, it fail to open the FB messenger app if display in Android APP application, and this application display web page using Chrome browser inside.
How to fix it?
You could provide a custom WebViewClient implementation to your WebView that checks for facebook.com links (or whatever links you like) and then explicitly fire an Intent so others can pickup the Action, instead of allowing the WebView to handle it as it sees fit.
webview.setWebViewClient(new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
try {
Uri uri = Uri.parse(url);
if (uri.getHost().contains("facebook.com")) {
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
return false;
}
} catch(Exception e){
e.printStackTrace();
}
return super.shouldOverrideUrlLoading(view, url);
}
});
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
I have two Applications with two web-views (a web-view in each). to simply explain my requirement with an example consider a user adds few items in his shopping trolley in first web-view in first app and then he press checkout and this will launched second web-view (which is in second app), result : he needs to see same shopping cart (in second web-view too).
I know sharing cookies between two webviews in a single app is not a problem and it is done in background by Android (CookieManager) however my case is different, I need to achieve the same in two different App.
Perhaps what I am looking for is how to share cookies/ session cookies between two webviews in different apps?
Note: I am restricted to targeted API 18
What I tried so far (apart of lots of useless reading) is :
getting cookie of the URL from the first webview and send it through
intent to second APP's activity
mWebView.setWebChromeClient(new WebChromeClient() {
});
mWebView.loadUrl(getPreferedURL().toString());
WebSettings settings = mWebView.getSettings();
settings.setJavaScriptEnabled(true);
settings.setAppCacheEnabled(true);
mWebView.setWebViewClient(new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (!url.contains("openCheckoutPage")) {
return false;
} else {
mCookie = CookieManager.getInstance().getCookieurl);
Intent fullScreenIntent = new Intent("com.bastami1982.webview.app.WebviewPanel_ACTION");
fullScreenIntent.putExtra("mUrl", mUrl);
fullScreenIntent.putExtra("mCookie", mCookie);
startActivity(fullScreenIntent);
return true;
}
}
});
set the cookie in second app webview
private void loadFullWebview() {
mLink = getIntent().getStringExtra("mUrl");
mLinkCookie = getIntent().getStringExtra("mCookie");
mFullScreenWebView = (WebView) findViewById(R.id.webView);
mFullScreenWebView.setWebChromeClient(new WebChromeClient() {
});
WebSettings settings = mFullScreenWebView.getSettings();
settings.setJavaScriptEnabled(true);
settings.setAppCacheEnabled(true);
if (mLinkCookie!=null) {
mCoockieManager.setAcceptCookie(true);
mCoockieManager.setCookie(mLink, mLinkCookie);
mCoockieManager.acceptCookie();
CookieSyncManager.getInstance().sync();
}
mFullScreenWebView.loadUrl(mLink);
mFullScreenWebView.setWebViewClient(new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return false;
}
});
}
I can confirm that I will get the cookie in second app but it seems webview is not using the cookie because the shopping cart is still empty.
I hope I explained it good enough to have some feedback , having said that please ask me for more info if it is needed
I checked the logcat in second App and I think this means that items in trolley can not be find (getItem) (I used Argos.co.uk) to test their shopping Trolley in another word I think cookie is not attached to the url correctly at loading ?! just guessing...
12-18 16:18:11.754 14675-14675 I/Web Console: Error while sending custom tracking data at https://d1af033869koo7.cloudfront.net/psp/argos-v1-001/default/20151216103020/pxfwk.gz.js:7
12-18 16:18:11.904 14675-14675 E/Web Console: Uncaught TypeError: Cannot call method 'getItem' of null at https://argospsp.px.247inc.net/pspxd.html#parentdomain=https://www.argos.co.uk&clientkey=argos-v1-001&version=20151216103020&pspv=default&n=1&caller=refloaded&l=1000&s=cookie:5
Try this...
private void loadFullWebview() {
mLink = getIntent().getStringExtra("mUrl");
mLinkCookie = getIntent().getStringExtra("mCookie");
mFullScreenWebView = (WebView) findViewById(R.id.webView);
WebSettings settings = mFullScreenWebView.getSettings();
settings.setJavaScriptEnabled(true);
settings.setAppCacheEnabled(true);
if (mLinkCookie!=null) {
mCoockieManager.removeSessionCookie();
CookieSyncManager.createInstance(this);
mCoockieManager.acceptThirdPartyCookies(mFullScreenWebView,true);
mCoockieManager.setAcceptCookie(true);
mCoockieManager.setCookie(mLink, mLinkCookie);
CookieSyncManager.getInstance().sync();
}
mFullScreenWebView.loadUrl(mLink);
mFullScreenWebView.setWebViewClient(new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return false;
}
});
}
It's been a while since this was asked, but I just had to do something similar (for different reasons)...
What worked for me was to create a service to share the values between the apps.
In my particular case, it was for mobile devices that were managed - so we controlled what is installed on the devices. Since we controlled things, I created a separate app install to have a service which was independent from both of the apps that each had an embedded webview.
The webviews would push cookies to the service when they were updated via monitoring the webview's page loads and querying for cookies at that point. They would load cookies up from the service when the app was foregrounded so they would get anything that had been set while they were backgrounded and the other app was active - and those cookies were added to the webview via the CookieManager.
In the case where there is a family of apps on the PlayStore, it could be done similarly, but there would need to be a service in each app for the other app to connect to and listen from.... If there are more than 2 apps, it could get kind of complex - so might need some creative patterns to handle pulling from all the apps when foregrounded, and keeping track of timestamps to sort out which ones were more recently saved so they overwrite the others....
Or, if one app will always be present, it can be the "main" app that contains the "service", and things will work exactly as what I've done.
Sorry no code here, figured I'd share the approach in case anyone else needs the idea.
I realized from working today that there's an easier way to do this, so answer has been edited to give the most complete answer I could.
Granted, this is Xamarin.Android, but it should work the same for Android Java (maybe kotlin too?):
I recommend doing the cookie getting in OnPageFinished override, but you could theoretically do it anywhere you have an Android.Webkit.CookieManager.Instance context. The reason I recommmend doing it in OnPageFinished() is because you should have the full set of cookies after your page loads.
Set your WebView to use a customized WebViewClient as such, which will allow you to override the OnPageFinished() method, thus getting the cookies at just the right time. If you don't want this overhead after you've got the cookies then you can always switch the WebViewClient or WebView to a vanilla unmodified WebViewClient.
Here's setting this client (optional)
FragmentContainerLayout = inflater.Inflate(Resource.Layout.Tab0FragLayout, container, false); }
Wv = FragmentContainerLayout.FindViewById<WebView>(Resource.Id.webView1);
Wv.SetWebViewClient(new LoginWebViewClient());
and here's getting the cookie from
APP SENDING COOKIE:
public class LoginWebViewClient : WebViewClient
{
public override void OnPageFinished(WebView view, string url)
{
RunPageCommands(view);
base.OnPageFinished(view, url);
}
public static void RunPageCommands(WebView w)
{
try
{
//replace this with yourDomain
string exampleDomain = "https://www." + "example.com"+ #"/"; //or .net or .tv etc
// get the cookie header in context
string cookieHeader = Android.Webkit.CookieManager.Instance.GetCookie(exampleDomain);
}
catch { return; /*cookie getting failed, nothing to do*/ }
ShareCookieWithAnotherApp(cookieHeader, exampleDomain);
}
}
if you don't need to get the cookie every time then you would not want to use an extended WebViewClient after you've got the cookie .. as it will slow down your app. You can alternatively just get the cookie header anywhere in the app with the CookieManager and pass it as an argument to any
You can reset the WebViewClient to the stock one by nulling it out WebView.SetWebViewClient(null) then set it back to the unextended version
then, in the context of an Activity, do something like this, which creates an Intent for another app to respond, and be careful here if your cookie has security tokens in it.
App SENDING cookie:
public void ShareCookieWithAnotherApp(string cookie, string domain){
Intent intent = new Intent();
intent.SetClassName("com.appstore", "com.appstore.MyBroadcastReceiver");
intent.SetAction("com.appstore.MyBroadcastReceiver");
intent.PutExtra("CookieSyncExtra", cookie);
intent.PutExtra("CookieDomainExtra", domain);
SendBroadcast(intent);
}
in the app receiving cookie, extend broadcast receiver:
public class MyBroadcastReceiver : BroadcastReceiver {
//this fires when it receives the broadcast from the sending app
public override void OnReceive(Context context, Intent intent) {
// set the CM to accept cookies
CookieManager.Instance.SetAcceptCookie(true);
//and set the cookie from your extras
CookieManager.Instance.SetCookie(intent.GetStringExtra("CookieDomainExtra"), intent.GetStringExtra("CookieSyncExtra"));
}
}
Manifest (receiving app), set the intent-filter :
<receiver
android:name=".MyBroadcastReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="first_app_packagename" />
</intent-filter>
</receiver>
and in your Activity of the receiving app:
//OnCreate
MyBroadcastReceiver MyReceiver = new MyBroadcastReceiver();
//I put this OnResume() but you can experiment with different placements
RegisterReceiver(MyReceiver , new IntentFilter("first_app_packagename"));
then you can use the WebView.LoadUrl(string url) after you've SetCookie in the receiving app
you can also attach the cookie header manually using the Dictionary<string, string> overload
Dictionary<string, string> cookieHeaderDictionary = new Dictionary<string, string>();
cookieHeaderDictionary.Add("Cookie", cookieString);
LoadUrl(url, cookieHeaderDictionary);
source: How to send data between one application to other application in android?
i am trying to pass a url which start with www.example.com/xxxxxxx in my webview it can be anything in the place of xxxxxxx. see the code you will understand
private class MyWebViewClient extends WebViewClient {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (Uri.parse(url).getHost().equals("http://www.example.com"+"*")) {
// This is my web site, so do not override; let my WebView load the page
return false;
}
// Otherwise, the link is not for a page on my site, so launch another Activity that handles URLs
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(intent);
return true;
}
}
i want to pass any url from my website only else open it in default browser.
Uri.getHost() return the host from the authority, you should try to use if (Uri.parse(url).getHost().equals("www.example.com")) or if (Uri.parse(url).getHost().equals("example.com")).
Also, as a good practice, you should reverse your equals() statement like this: "example.com".equals(Uri.parse(url).getHost()) as you know that "example.com" will never be null and, as such, never throw a NullPointerException.