Calling phone number from HTML5 android - javascript

I built an android app with HTML5 and Javascript. I want to enable users to dial numbers but it is not working:
a href="tel:+201224001111">+201224001111/a>
I also added this line to android manifest xml:
uses-permission android:name="android.permission.CALL_PHONE" /

Daina, as I understand you're loading html5 into webview, right? In this case you need to set WebViewClient and override shouldOverrideUrlLoading method like:
public static final String TEL_TYPE = "tel:";
webView.setWebViewClient(new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.contains(AppConstants.TEL_TYPE)) {
try {
activity.startActivity(new Intent(Intent.ACTION_DIAL, Uri.parse(url)));
return true;
} catch (ActivityNotFoundException e) {
//you can show toast here
return true;
}
}
return super.shouldOverrideUrlLoading(view, url);
}
});

Compare yours with
Call me at +1 (555) 555-5555
See https://mobiforge.com/design-development/mobile-friendliness-101-adding-click-to-call-links-to-a-website
This should work as click-to-call.

a better approach for HTML is:
call test
and also notice that there is no need for permissions in this method.
if you like to use internal function for call in android , you can run intents for call via :
String phnumber = "+155555555";
Intent callIntent = new Intent(Intent.ACTION_CALL);
callIntent.setData(Uri.parse("tel:" + phnumber));
startActivity(callIntent);
which needs call permission.
also this method below do not need permissions:
String phnumber = "+155555555";
Intent callIntent = new Intent(Intent.ACTION_DIAL);
callIntent.setData(Uri.parse("tel:" + phnumber));
startActivity(callIntent);
this will show the user dial pad and call button.
hope this will help!

Related

Is there a way to create bi-directional communication between a shell app and a webview in .NET 6 MAUI?

Recently I've had the assignment to create a bi-directional interop bridge between a shell app and a webpage in .NET MAUI. Not finding any way to solve this I had the idea of creating it in Xamarin.Forms first seeing as MAUI is a continuation on it.
After having created this app, I've tried to convert it over to MAUI using Microsoft's instructions on the dotnet/maui github wiki.
The main problem i'm encountering right now is that I've been using extensions on Android's WebViewRenderer, WebViewClient and Java.Lang.Object to be able to send and receive javascript to and from the WebView.
public class ExtendedWebViewRenderer : WebViewRenderer
{
private const String JavascriptFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
public ExtendedWebViewRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
Control.RemoveJavascriptInterface("jsBridge");
((ExtendedWebView)Element).Cleanup();
}
if (e.NewElement != null)
{
Control.SetWebViewClient(new JavascriptWebViewClient($"javascript: {JavascriptFunction}"));
Control.AddJavascriptInterface(new JsBridge(this), "jsBridge");
}
}
}
public class JavascriptWebViewClient : WebViewClient
{
private readonly String _javascript;
public JavascriptWebViewClient(String javascript)
{
_javascript = javascript;
}
public override void OnPageFinished(WebView view, String url)
{
base.OnPageFinished(view, url);
view.EvaluateJavascript(_javascript, null);
}
}
public class JsBridge : Java.Lang.Object
{
private readonly WeakReference<ExtendedWebViewRenderer> _extendedWebViewMainRenderer;
public JsBridge(ExtendedWebViewRenderer extendedRenderer)
{
_extendedWebViewMainRenderer = new WeakReference<ExtendedWebViewRenderer>(extendedRenderer);
}
[JavascriptInterface]
[Export("invokeAction")]
public void InvokeAction(String data)
{
if (_extendedWebViewMainRenderer != null && _extendedWebViewMainRenderer.TryGetTarget(out var extendedRenderer))
{
((ExtendedWebView)extendedRenderer.Element).InvokeAction(data);
}
}
}
All three of these are either not available right now, or will not be implemented in MAUI, since a lot of platform dependent code has been automated now. Which leaves me with the problem that I can't seem to figure out how to change my current code to accomplish the same thing in MAUI.
Seeing as MAUI is currently not even fully released, I was wondering if this is currently just not possible or if there is some workaround to make it possible.
Any help would be greatly appreciated.
Calling C# from a webview is actually extremely simple. Just navigate and then intercept that in c#.
the xaml:
<WebView WidthRequest="400" HeightRequest="400" Navigating="WebView1_Navigating">
<WebView.Source>`enter code here`
<HtmlWebViewSource>
<HtmlWebViewSource.Html>
<![CDATA[
<HTML>
<script>
function callCsharp(){
window.location.href = 'http://poc.MyFunction?name=john&country=DK';
}
</script>
<BODY
A link that triggers C#
<br>
<button onclick="callCsharp()" type="button">A button calling javascript</button>
</BODY>
</HTML>
]]>
</HtmlWebViewSource.Html>
</HtmlWebViewSource>
</WebView.Source>
The C#:
private async void WebView1_Navigating(object sender, WebNavigatingEventArgs e)
{
var urlParts = e.Url.Split(".");
if (urlParts[0].ToLower().Equals("http://poc"))
{
var funcToCall = urlParts[1].Split("?");
var methodName = funcToCall[0];
var funcParams = funcToCall[1];
Debug.WriteLine("Calling " + methodName);
// prevent the navigation to complete
e.Cancel = true;
// TODO smart parsing and type casting of parameters and then some reflection magic
}
}
MAUI's default WebView has the Eval and EvaluateJavaScriptAsync functions to call JavaScript code from C#:
Eval just executes the script string you pass in a fire-and-forget way.
EvaluateJavaScriptAsync needs to be awaited but also returns a string with a stringified result of the data that the script returned.
If you want to use callback/bridge methods to automatically receive data from the JavaScript side without any input from the C# side of the app, you will have to extend the default per-platform renderers to add that functionality. The good news is that there is an official tutorial on how to do it for Xamarin Forms at Customizing a WebView which is almost straightforward to port to .NET MAUI - you only have to change how renderers are registered.

Android Webview : How to catch click on the given local URL

I displaying the content in the webview. Content is loaded from the server API. Content can contain following links:
Register
How can i catch click on the #register.
I can do it with some HTML parser and append onClick event, but much better and easier will be to catch URL change to #register.
Many thanks for any advice.
Edit:
I tried the following example but without the luck
browser = (WebView) view.findViewById(R.id.intro_browser);
// Set Chrome instead of the standard WebView
browser.setWebViewClient(new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url){
Logger.d("TEST");
return true;
}
#Override
public void onLoadResource(WebView view, String url) {
Logger.d("URL IS:");
Logger.d(url);
if (url.startsWith("app://")) {
}
}
});
browser.getSettings().setJavaScriptEnabled(true);
browser.addJavascriptInterface(new WebViewJavaScriptInterface(getContext()),
Constants.Welcome.JAVASCRIPT_NAMESPACE);
//browser.getSettings().setAllowFileAccessFromFileURLs(true);
//browser.getSettings().setAllowUniversalAccessFromFileURLs(true);
browser.loadData(htmlContent, Constants.Welcome.MIME_TYPE, Constants.Welcome.ENCODING);
You need to create custom WebViewClient:
public class CustomWebViewClient extends WebViewClient {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
boolean result = false;
if (Objects.equal(url, "#register")) {
result = true;
//Do what you want here
}
return result;
}
}
and then you need to give it to WebView:
webView.setWebViewClient(new CustomWebViewClient());
returning true in shouldOverrideUrlLoading means that you handled the Url while returning false means that WebView should handle it.
Add an intent filter in your manifest so that links starting with "yourapp://" or something similar will launch your app.
If you are generating the web content, then generate links for your app.
Register
If you are not generating the content, then use webview.load("javascript:// code to replace register links to yourapp://").

Unable to execute Js code in WebView in Cordova Project

I have two activity, one main activity(A) is an CordovaActivity, then I use intent to start another activity(B), in B i have an WebView(not CordovaActivity), and after I use this webview to load a simple webpage (alert something), I found the js code is not executed at all, even if I enable javascript by calling setttings.setJsenabel(true);
I start activity B from A
Load Url from webview in Activity B
simple web page
in the device, it does not alert anything
However, if I change the webview to CordovaWebView instead of the original Android native one, it works.....
That's because plain WebView doesn't support showing alerts by itself. You need to provide a WebChromeClient to it that implements WebChromeClient.onJsAlert method. For example:
mywebView.setWebChromeClient(new WebChromeClient() {
#Override
public boolean onJsAlert(
WebView view, String url, String message, final JsResult result) {
new AlertDialog.Builder(view.getContext())
.setTitle("Alert")
.setMessage(message)
.setPositiveButton("Ok",
new AlertDialog.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
result.confirm();
}
}).setCancelable(false).create().show();
return true;
}
});

Android button as WebView click link for Logout (or delete sessionId)

In my WebView android, I want the user to logout on button click or any other way.
I don't want to show any webpage after clicking that button. After that app should auto-exit(System.exit(0) will do that)
Click I want to send is:
http://192.100.1.1:10000/app?service=direct/1/UserSummary/$UserBorder.logoutLink
What's the best way of doing this?
Use Rest to make a get call to this URL.
To make it use an async task:
See an example here.
In your do in background do:
protected String doInBackground(String... urls) {
String url = urls[0];
HttpClient httpclient = new DefaultHttpClient(getHttpParams());
HttpResponse response = null;
try {
switch (taskType) {
HttpGet httpget = new HttpGet(url);
response = httpclient.execute(httpget);
} catch (Exception e) {
Log.e(TAG, e.getLocalizedMessage(), e);
}
return response;
}
private HttpParams getHttpParams() {
HttpParams htpp = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(htpp, CONN_TIMEOUT);
HttpConnectionParams.setSoTimeout(htpp, SOCKET_TIMEOUT);
return htpp;
}
The main reason behind this question was this logout link has nothing to display. So I wanted something like click to logout. Simple Android button in Main Activity didn't help for this.
So, I created new blank activity, used same layout I was using, but instead of showing blank layout, I added ProcessDialog and made it non-cancel-able as follows:
final ProgressDialog pd = new ProgressDialog(Logout.this);
pd.setCancelable(false);
pd.setCanceledOnTouchOutside(false);
pd.setMessage("Please wait...");
pd.show();
And in public void onPageFinished(WebView view, String url) method, I dismissed this ProcessDialog and fired an intent for login page of my app. Also overridden the public boolean shouldOverrideUrlLoading(WebView view, String url) to prevent it from going to out of app.
After that, simply webView.loadUrl(url);.
This way I fixed my problem.
(If you know better and simpler way of doing it, please post.)

Disabling caching, cookies and everything else in a WebView

I have a webservice that I am trying to authenticate with in the background using a webview. When I initially send the request it will work appropriately (failure/success based on credentials), but after it seems like I am getting a cached response.
Here is my webview setup code:
WebView browser = new WebView(this);
WebSettings settings = browser.getSettings();
settings.setJavaScriptEnabled(true);
settings.setSavePassword(false);
settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
settings.setAppCacheEnabled(false);
browser.setWebChromeClient(new WebChromeClient() {
public void onProgressChanged(WebView view, int progress) {
Log.d("BROWSERPROGRESS", Integer.toString(progress));
}
});
jsInterface = new AddAccountJSInterface();
browser.addJavascriptInterface(jsInterface, "ADDACCOUNTJSINTERFACE");
browser.setWebViewClient(new AddAccountClient(this));
So as you may see I have two additional classes controlling my webView:
An object that provides an interface for javascript (AddAccountJSInterface)
A WebViewClient
Additionally I do have a WebChromeClient, but it's only there for debugging and I'm pretty sure that it won't interfere with anything.
The JS interface simply provides an easy way of getting the body HTML for performing analysis, so I'm confident that isn't the issue either.
The WebViewClient has the following code in it which does most of the "custom" work for routing based on various responses from the webservice.
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if(url.contains(INSTALL_PREFIX)) {
HashMap<String, String> params = extractParameters(url);
verificationComplete(params);
return true;
}
return false;
}
#Override
public void onPageFinished(WebView view, String url){
if(invalidShop(view)) {
Toast.makeText(context, context.getString(R.string.no_find_shop), Toast.LENGTH_SHORT).show();
shopAddressField.requestFocus();
replaceUiElements(loadingBar, addAccountButton);
} else if(url.contains(ADMIN_AUTH_LOGIN)) {
if(invalidLogin(view)) {
Toast.makeText(context, context.getString(R.string.invalid_login),Toast.LENGTH_SHORT).show();
emailField.requestFocus();
replaceUiElements(loadingBar, addAccountButton);
} else {
String email = emailField.getText().toString();
String password = passwordField.getText().toString();
String submitJS = String.format(FORM_SUBMISSION_JS, email, password);
jsInterface.setInnerHTML("");
browser.loadUrl(submitJS);
}
}
}
In my activity I have 3 text fields that I need to fill followed by clicking a button to submit it. The activity then takes in the data from 3 text fields (shopAddressField, usernameField, passwordField) and then executes some javascript that populates some form data (which was loaded in the invisible webView) then clicks the submit button.
It is the last part that is messing up, which appears to be caching the response from the server (perhaps using cookies?) and return that instead of asking the server if the data is correct or not.
A bit of clarification:
JSInterface is simply a Java object that allows me to execute javascript on my webview which is tied to a function within that object. In my case my JSInterface has one function which is setInnerHtml(String html).
This is the javascript that is executed on the webview:
javascript:window.ADDACOUNTJSINTERFACE.setInnerHTML(document.body.innerHTML)
And this is the setInnerHtml function:
public void setInnerHtml(String innerHtml) {
this.innerHtml = innerHtml;
}
So when I actually execute jsInterface.setInnerHtml("") I'm just over-writing the HTML that was pulled in (to be sure I'm not getting my old data from there for some reason).
As for my submitJS it is once again some Javascript that is executed on my webView as follows:
// submitJS will be something like this once all the credentials have been set
// Note: I know that the server will make jQuery available
// Note: Much of the Java string formatting has been removed to help clarify
// the code.
String submitJS =
"javascript:(function() {
$('login-input').value='username';
$('password').value='password';
$('sign-in-form').up().submit();
})()"
// I then simply get the webview to execute the javascript above
webView.loadData(submitJS);
So it turns out the problem wasn't based around the Caching, and possibly not cookies.
When executing javascript on your webView it does this in a separate thread and can be quite slow. This lead to a race condition which caused code to be executed in the wrong order.
I've solved this problem by using a Semaphore as a Mutex. This allows me to prevent my getter from returning before the Javascript on the webView is able to execute.
The interface I created now looks like this:
private class AddAccountJSInterface {
private final String TAG = getClass().getName().toUpperCase();
private Semaphore mutex = new Semaphore(1, false);
private String innerHTML;
public void aquireSemaphore() {
Log.d(TAG, "Attempting to lock semaphore");
try {
mutex.acquire();
} catch(InterruptedException e) {
Log.d(TAG, "Oh snap, we got interrupted. Just going to abort.");
return;
}
Log.d(TAG, "Semaphore has been aquired");
}
#SuppressWarnings("unused")
public void setInnerHTML(String html) {
this.innerHTML = html;
Log.d(TAG, "setInnerHTML is now releasing semaphore.");
mutex.release();
Log.d(TAG, "setInnerHTML has successfully released the semaphore.");
}
public synchronized String getInnerHTML() {
Log.d(TAG, "getInnerHTML attempting to aquire semaphore, may block...");
String innerHTML = "";
try {
mutex.acquire();
Log.d(TAG, "getInnerHTML has aquired the semaphore, grabbing data.");
innerHTML = this.innerHTML;
Log.d(TAG, "getInnerHTML no longer needs semaphore, releasing");
mutex.release();
} catch (InterruptedException e) {
Log.d(TAG, "Something has gone wrong while attempting to aquire semaphore, aborting");
}
return innerHTML;
}
}
Now the way I use this in my code is as follows:
// I have access to the jsInterface object which is an instance of the class above as well as a webView which I will be executing the javascript on.
String getInnerHtmlJS = "javascript:window.MYJSINTERFACE.setInnerHTML(document.body.innerHTML);"
jsInterface.aquireSemaphore()
// Execute my JS on the webview
jsInterface.loadUrl(getInnerHtmlJS)
// Now we get our inner HTML
// Note: getInnerHTML will block since it must wait for the setInnerHTML (executed via the JS) function to release the semaphore
String theInnerHTML = jsInterface.getInnerHTML();

Categories