74

I am trying to get my android webview app to open tel: links to the phone. Every time I open up a telephone link it works great and opens up the phone. However once I am done with my call and go back to the app it is at a page that says "Web Page Not Found tel:0000000000". Then I have to hit the back button once more to get to the page that I clicked the telephone number on.

Is there a way to get it to open the TEL link without trying to find the page in webview as well as opening it up on the phone?

This is code I am using in WebView to override its handling of the TEL and Mailto links:

        public boolean shouldOverrideUrlLoading(WebView view, String url) {
        if (url.startsWith("mailto:") || url.startsWith("tel:")) { 
                Intent intent = new Intent(Intent.ACTION_VIEW,
                        Uri.parse(url)); 
                startActivity(intent); 
                } 
        view.loadUrl(url);
        return true;
        }

Any help would be appreciated. I have spent the last 2 hours scouring goodle and have failed to produce any answers.

Jeff Thomas
  • 4,728
  • 11
  • 38
  • 57
  • 1
    Try ACTION_DIAL for the tel: link? – EboMike Dec 02 '10 at 18:13
  • Wait, the docs actually say that ACTION_VIEW is fine: http://developer.android.com/reference/android/content/Intent.html Never mind then... – EboMike Dec 02 '10 at 18:33
  • Stupid question: Did you set up the `WebViewClient` correctly? Does everything else work? – EboMike Dec 02 '10 at 18:34
  • Yes... everything else is functioning normally except for the tel: links. And even those work, it is just that when you are dont with your phone call and go back to the app it is sitting on a page that says not found. – Jeff Thomas Dec 02 '10 at 19:07
  • @EboMike: I would argue that you were right the first time: http://developer.android.com/guide/appendix/g-app-intents.html -- I would not trust that "Note how the VIEW action does what what is considered the most reasonable thing for a particular URI" note in the docs. – CommonsWare Dec 02 '10 at 19:08
  • I just tried changing it to ACTION_DIAL and it gives me the same issue. – Jeff Thomas Dec 02 '10 at 19:09
  • Sorry, comrades. I'm a little lost. Exactly where I place and call that function? Here is my [code](http://pastie.org/private/pfqcqqvikb6tvb8afcdow): – candlejack Mar 03 '14 at 16:19

11 Answers11

121

OK so I solved the issue I think. I just needed to separate the URL overrides as follows:

public boolean shouldOverrideUrlLoading(WebView view, String url) {
    if (url.startsWith("tel:")) { 
        Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(url)); 
        startActivity(intent);
        view.reload();
        return true;
    }

    view.loadUrl(url);
    return true;
}

Now my regular links work as well as the tel links. I can also add in there for geo: links if I need to and it will not give me the issue that I was having before to open up maps on the phone.

lesyk
  • 3,979
  • 3
  • 25
  • 39
Jeff Thomas
  • 4,728
  • 11
  • 38
  • 57
  • 3
    Duh. I just realized that you're calling view.LoadUrl(url) on your tel: link. You might as well just add a `return true` after `startActivity()`. – EboMike Dec 02 '10 at 19:55
  • 6
    Add to Manifest. – jasonflaherty Mar 02 '12 at 06:49
  • 10
    @jasonflaherty actually the CALL_PHONE permission is for ACTION_CALL not ACTION_DIAL. So the above code works good even without the permission. – Elyess Abouda Nov 25 '13 at 11:51
  • 4
    You don't need the view.loadUrl(url); Returning true will cause the WebView to override the URL and handle it how it see's fit. – Matt Gaunt Nov 29 '13 at 13:33
  • 5
    Please don't replicate the code above as-is. Return false from the callback instead of calling view.loadUrl instead. Calling loadUrl introduces a subtle bug where if you have any iframe within the page with a custom scheme URL (say ) it will navigate your app's main frame to that URL most likely breaking the app as a side effect. – marcin.kosiba Feb 07 '14 at 12:37
  • This is true answer. "return true;" line was critical for me. I had something another. Also I didn't add permission to Manifest. – Denis Kutlubaev Feb 28 '14 at 12:27
  • Is not necessary to use "reload" and "loadUrl" methods. – pablopatarca Feb 13 '20 at 12:15
  • 1
    @pablopatarca, agree. In case of `loadUrl` we can simply return `false`. – CoolMind Aug 25 '20 at 16:27
60

Rather than call loadUrl(url), just return false for the URLs that should not be overridden:

public boolean shouldOverrideUrlLoading(WebView view, String url) {
    if( URLUtil.isNetworkUrl(url) ) {
        return false;
    }

    // Otherwise allow the OS to handle it
    Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
    startActivity( intent ); 
    return true;
}

I've found VIEWing tel: works as expected on all phones we've tried it with. No need to special-case it because of the DIAL action.

I've noticed YouTube videos and the like don't work in WebViews, so you may want to detect those as well.

The whole process could probably be generalized for all sorts of URIs by querying the PackageManager for Activities that handle your URI that are also not the embedded browser. That might be overkill and get confused by other installed browsers.

Anm
  • 3,291
  • 2
  • 29
  • 40
19

According to the documentation and based on my experience, Intent.ACTION_VIEW is perfectly fine to parse tel: , sms: , smsto: , mms: and mmsto: links.

Here's a 5 in 1:

@Override
    public boolean shouldOverrideUrlLoading(WebView webview, String url)
    {
     if (url.startsWith("tel:") || url.startsWith("sms:") || url.startsWith("smsto:") || url.startsWith("mms:") || url.startsWith("mmsto:"))
       { 
         Intent intent = new Intent(Intent.ACTION_VIEW,Uri.parse(url)); 
         startActivity(intent); 
         return true;
       }
    return false;
   }
Pedro Lobito
  • 94,083
  • 31
  • 258
  • 268
16

Note :- After Android Nouget shouldOverrideUrlLoading is Deprecated

You need to use shouldOverrideUrlLoading along with shouldOverrideUrlLoading for better support. Also you might want to check if url have mailto: or tel:, which are used in HTML5 to trigger mail client and phone dial respectively.

A complete solution will look like this now

    @SuppressWarnings("deprecation")
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        if (url.startsWith("mailto:")) {  
            //Handle mail Urls
            startActivity(new Intent(Intent.ACTION_SENDTO, Uri.parse(url)));
        } else if (url.startsWith("tel:")) {
            //Handle telephony Urls
            startActivity(new Intent(Intent.ACTION_DIAL, Uri.parse(url)));
        } else {
            view.loadUrl(url);
        }
        return true;
    }

    @TargetApi(Build.VERSION_CODES.N)
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
        final Uri uri = request.getUrl();
        if (uri.toString().startsWith("mailto:")) {
            //Handle mail Urls
            startActivity(new Intent(Intent.ACTION_SENDTO, uri));
        } else if (uri.toString().startsWith("tel:")) {
            //Handle telephony Urls
            startActivity(new Intent(Intent.ACTION_DIAL, uri));
        } else {
            //Handle Web Urls
            view.loadUrl(uri.toString());
        }
        return true;
    }
Hitesh Sahu
  • 41,955
  • 17
  • 205
  • 154
  • Perfect... Handles pre-nougat and nougat. Thank you – Sudheesh R Aug 02 '17 at 06:37
  • Thanks! Use `@Suppress("DEPRECATION")` instead of `@SuppressWarnings("deprecation")`. You can create one method for both events. Use `view.getContext()` to insert before `startActivity()`. – CoolMind Aug 25 '20 at 14:07
  • Can anyone please explain what exactly to do if I get :"error: method startActivity in class ContextCompat cannot be applied to given types; startActivity(new Intent(Intent.ACTION_SENDTO, uri)); ^ required: Context,Intent,Bundle found: Intent reason: actual and formal argument lists differ in length" I have implemented the solution in another class – Undry Mar 09 '21 at 05:31
4
public class MainActivity extends Activity {

private static final String HTML ="<!DOCTYPE html><html><body><a 
href='tel:867-5309'>Click here to call!</a></body></html>";
private static final String TEL_PREFIX = "tel:";

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    WebView wv = (WebView) findViewById(R.id.webview);
    wv.setWebViewClient(new CustomWebViewClient());
    wv.loadData(HTML, "text/html", "utf-8");
}

private class CustomWebViewClient extends WebViewClient {

    @Override
    public boolean shouldOverrideUrlLoading(WebView wv, String url) {
        if(url.startsWith(TEL_PREFIX)) {
            Intent intent = new Intent(Intent.ACTION_DIAL);
            intent.setData(Uri.parse(url));
            startActivity(intent);
            return true;
        }
        return false;
    }
}

}

This was the fix i found. You have to use this method.

wv.setWebViewClient(new CustomWebViewClient());
Vimukthi Sineth
  • 310
  • 2
  • 8
3

If there is no WebViewClient assigned to WebView, by default WebView will ask Activity Manager to choose the proper handler for the URL. If a WebViewClient is provided, you should handle different URLs yourself and returns true in WebViewClient.shouldOverrideUrlLoading(), otherwise it will try to send request to the URL and get an error, then triggers onReceiveError().

Check document: WebViewClient.shouldOverrideUrlLoading

     @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        if (url.startsWith("tel:")) { 
            // ...TODO: launch a Dial app or send SMS or add to contact, etc...
            return true;
        }
        else if (url.startsWith("mailto:")) {
            // ...TODO: send email to someone or add to contact, etc...
            return true;
        }
        else {
            // ...TODO: Handle URL here
            boolean handled = yourHandleUrlMethod(url);
            return handled;
        }
    }
evanchin
  • 2,028
  • 1
  • 22
  • 25
3

Some constants are static in WebView class. You should use Intent.ACTION_VIEW instead of Intent.ACTION_DIAL or ACTION_SENDTO:

@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
    String url = request.getUrl().toString();
    if (url.startsWith(WebView.SCHEME_TEL)
            || url.startsWith(SCHEME_SMS)
            || url.startsWith(WebView.SCHEME_MAILTO)
            || url.startsWith(WebView.SCHEME_GEO)) {
        
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setData(Uri.parse(url));
        startActivity(intent); // view.context.startActivity(intent);
    }
    return true;
}
javakam
  • 127
  • 1
  • 5
1

For those who tried to use @jeff Thomas answer but got below error:

cannot find symbol view.startActivity(intent);

You can use this code:

public boolean shouldOverrideUrlLoading(WebView view, String url) {
    if (url.startsWith("tel:")) { 
        Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(url)); 
        view.getContext().startActivity(intent);
        view.reload();
        return true;
    }

    view.loadUrl(url);
    return true;
}

I just changed startActivity(intent) to view.getContext().startActivity(intent) and thats worked for me.

Alireza
  • 2,319
  • 2
  • 23
  • 35
1
public boolean shouldOverrideUrlLoading(WebView view, String url)
       {Uri query_string=Uri.parse(url);
        String query_scheme=query_string.getScheme();
        String query_host=query_string.getHost();
        if ((query_scheme.equalsIgnoreCase("https") || query_scheme.equalsIgnoreCase("http"))
            && query_host!=null && query_host.equalsIgnoreCase(Uri.parse(URL_SERVER).getHost())
            && query_string.getQueryParameter("new_window")==null
           )
           {return false;//handle the load by webview
           }
        try
           {Intent intent=new Intent(Intent.ACTION_VIEW, query_string);
            String[] body=url.split("\\?body=");
            if (query_scheme.equalsIgnoreCase("sms") && body.length>1)
               {intent=new Intent(Intent.ACTION_VIEW, Uri.parse(body[0]));
                intent.putExtra("sms_body", URLDecoder.decode(body[1]));
               }
            view.getContext().startActivity(intent);//handle the load by os
           }
        catch (Exception e) {}
        return true;
       }
diyism
  • 12,477
  • 5
  • 46
  • 46
  • 1
    Could you explain your solution a bit? Especially the test with the URL_SERVER (which is missing, by the way) might not be clear to everyone. – Ivin Mar 21 '13 at 10:17
  • URL_SERVER is "http://your.app.domain.com", and "return false;" means that the pages in your site will be opened in the app and won't be opened in the android browser – diyism Mar 22 '13 at 14:05
0
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    WebView wv = (WebView) findViewById(R.id.webview);
    wv.setWebViewClient(new CustomWebViewClient());
    wv.loadData(HTML, "text/html", "utf-8");
}

private class CustomWebViewClient extends WebViewClient {
    @SuppressWarnings("deprecated")
    @Override
    public boolean shouldOverrideUrlLoading(WebView wv, String url) {
        if(url.startsWith(TEL_PREFIX)) {
            Intent intent = new Intent(Intent.ACTION_DIAL);
            intent.setData(Uri.parse(url));
            startActivity(intent);
            return true;
        }
        return false;
    }
Naju Mat Isa
  • 55
  • 1
  • 7
  • i tried to use this method posted above, but when i added "shouldoverrideurlloading", there's error saying this method is deprecated. is there any restriction of API to use this code?.. i tried to install this inside my phone but, the apps kills itself. How can i solve this?..please help – Naju Mat Isa Feb 20 '18 at 06:17
0

Looking at Hitesh Sahu and kam C answers I got a solution.

In API 21 it raised an exception: "android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?".

Also an activity may not be found, as noticed by diyism. So, I handled these situations.

class MyWebViewClient : WebViewClient() {

    @Suppress("DEPRECATION")
    override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
        if (view != null && url != null) {
            return resolveUri(view.context, Uri.parse(url))
        }
        return super.shouldOverrideUrlLoading(view, url)
    }

    @TargetApi(Build.VERSION_CODES.N)
    override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
        val uri = request?.url
        if (view != null && uri != null) {
            return resolveUri(view.context, uri)
        }
        return super.shouldOverrideUrlLoading(view, request)
    }

    private fun resolveUri(context: Context, uri: Uri): Boolean {
        val url = uri.toString()
        URL_SCHEMES.forEach {
            if (url.startsWith(it)) {
                val intent = Intent(Intent.ACTION_VIEW).apply {
                    data = uri
                    addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                }
                try {
                    context.startActivity(intent)
                } catch (e: ActivityNotFoundException) {
                }
                return true
            }
        }
        return false
    }

    companion object {
        private val URL_SCHEMES = arrayOf(WebView.SCHEME_TEL,
            WebView.SCHEME_MAILTO, WebView.SCHEME_GEO, "sms:", "smsto:", "mms:", "mmsto:")
    }
}
CoolMind
  • 26,736
  • 15
  • 188
  • 224