Android WebView with https loadUrl shows blank/empty page

September 28, 2010

I recently encountered this problem while trying to develop a WebView that is supported on Doughnut (1.6) and above. When presented with a https url the WebView just renders a blank page.

It seems that until Froyo, Android didn’t provide a public API to let you manually decide if you wanted to proceed to an untrusted web site via a WebView.

Note that in this case it is not even that the web site was untrusted (in the conventional sense) – it is because Thawte is not in the default list of trusted certificate authorities on Android. If you use the standard web browser on Android, the browser presents a typical warning dialog (as presented below) that enables you to accept the certificate and carry on.

Invalid certificate warning

If you are using Froyo as the target SDK then you can use:

engine = (WebView) findViewById(R.id.my_webview);
engine.setWebViewClient(new WebViewClient() {
 public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error) {
 handler.proceed() ;
 }
}

However, if you are not using Froyo then it seems you are out of luck. After some research, I concluded that I had the following options:

a) intercept all page requests and forward to external web browser if url begins with https (not a clean or nice user experience and totally unnecessary for Froyo and above)
b) add certificate of website to local keystore (in this case I am serving multiple web pages and the origin of many of these is not known until runtime)
c) make Froyo the minSDK and discard previous versions of Android (not a suitable option)
d) hack and use some private apis (the option described below)

To solve this problem we have to use a private interface (not published on SDK but present in real SDK runtime). As you can see in the Android src tree, the onReceivedSslError is indeed present (and used – it simply cancels the request) in Doughnut. However, this method is not presented to us in the SDK  - it is hidden because it contains a parameter type SslError which is located in a hidden package.  Therefore, we need to copy these src files into our project so that we can access them:

1) Copy WebViewClient.java into the package “android.webkit” within your src folder.
2) Copy SslError.java into the package “android.net.http” within your src folder.

Src files

3) Since we replicated the paths to the src files in the SDK, our code to override onSslError above now works on Android 1.6.

Caution: bear in mind that we are using a private API and Google reserve the right to change private APIs at any time – though in this case it is unlikely since they’ve now made this available in Froyo.

About these ads

23 Responses to “Android WebView with https loadUrl shows blank/empty page”

  1. Bano Says:

    Hi, I have used this method and other method (like this http://wongcr.blogspot.com/2009/03/disabling-certificate-validation-in.html) and I can’t see a https page in the WebView. Can you help me?


  2. I can try. Your request for help is a bit vague though…

    • Bano Says:

      Sorry, I did’t know if I should write a large post. My problem is with the HttpPost connections, I create an “DefaultHttpClient” and a “HttpPost”, next I create a list of values to send and finally execute it.

      This work in http conections, but in https conections I try the methods I find on Internet and don’t get it to work (Get not trusted certificate).

      The last code (with custom SSLShocketFactory and TrustManager):
      try {
      // //
      SchemeRegistry schemeRegistry = new SchemeRegistry();

      schemeRegistry.register(new Scheme(“https”,new EasySSLSocketFactory(), 443));

      HttpParams params = new BasicHttpParams();
      SingleClientConnManager mgr = new SingleClientConnManager(params, schemeRegistry);

      DefaultHttpClient hc = new DefaultHttpClient(mgr, params);
      // /

      // DefaultHttpClient hc = new DefaultHttpClient();
      ResponseHandler res = new BasicResponseHandler();
      HttpPost postMethod = new HttpPost(DIRECCION);
      List nameValuePairs = new ArrayList(3);
      nameValuePairs.add(new BasicNameValuePair(“usuario”, nom));
      nameValuePairs.add(new BasicNameValuePair(“tipo_firma”, tip));
      nameValuePairs.add(new BasicNameValuePair(“string_img”, img));
      postMethod.setEntity(new UrlEncodedFormEntity(nameValuePairs));
      String response = hc.execute(postMethod, res);
      return response;
      } catch (ClientProtocolException e) {
      pasos = “Excepción: ” + e.toString();
      } catch (IOException e) {
      // “Error 1: Unable to establish communication with the server. Check your configuration.”
      pasos = pasos + “Excepción: ” + e.toString();
      }

      if you can help me, it would very helpfull.

      PD: Sorry for my bad english…


  3. The best way would be to add the server’s public certificate to your Android App’s trusted store:

    http://blog.crazybob.org/2010/02/android-trusting-ssl-certificates.html

    Otherwise you could simply stop checking all hosts for their certificate:

    http://stackoverflow.com/questions/995514/https-connection-android/1000205#1000205

    • Bano Says:

      Finally, your post give me an idea. I change the DefaultHttpClient for HttpsURLConection, using OutputStream and InputStream to the comunication. Next I apply the second link you give me (I visited it yet but didn’t help me in my previously idea).

      I can’t use the SSL certificate creator because I haven’t direct access to the server of the proyect.

      the code of Send:
      try {
      // //
      String location = “usuario=” + nom + “&tipo_firma=” + tip + “&string_img=” + img;
      URL url = new URL(DIRECCION);
      trustAllHosts();
      HttpsURLConnection https = (HttpsURLConnection) url.openConnection();
      https.setHostnameVerifier(DO_NOT_VERIFY);
      https.setRequestMethod(“POST”);
      https.setDoOutput(true);
      https.setUseCaches(false);
      https.setRequestProperty(“Content-Type”,
      “application/x-www-form-urlencoded”);
      https.connect();
      OutputStream ost = https.getOutputStream();
      DataOutputStream out = new DataOutputStream(ost);
      out.writeBytes(location);
      out.flush();
      ost = null;

      InputStream ist = https.getInputStream();
      BufferedReader in = new BufferedReader(new InputStreamReader(ist));
      StringBuffer sb = new StringBuffer();
      char[] c = new char[1];
      while (in.read(c, 0, 1) == 1) {
      sb.append(c[0]);

      }
      String response = sb.toString();
      // /
      return response;
      } catch (ClientProtocolException e) {
      pasos = “Excepción: ” + e.toString();
      } catch (IOException e) {

      // “Error 1: Unable to establish communication with the server. Check your configuration.”
      pasos = pasos + “Excepción: ” + e.toString();
      }

      Next adding the trust part:
      // always verify the host – dont check for certificate
      final static HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {
      public boolean verify(String hostname, SSLSession session) {
      return true;
      }
      };
      /**
      * Trust every server – dont check for any certificate
      */
      private static void trustAllHosts() {
      // Create a trust manager that does not validate certificate chains
      TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
      public java.security.cert.X509Certificate[] getAcceptedIssuers() {
      return new java.security.cert.X509Certificate[] {};
      }

      public void checkClientTrusted(X509Certificate[] chain,String authType) throws CertificateException {}
      public void checkServerTrusted(X509Certificate[] chain,String authType) throws CertificateException {}
      } };

      // Install the all-trusting trust manager
      try {
      SSLContext sc = SSLContext.getInstance(“TLS”);
      sc.init(null, trustAllCerts, new java.security.SecureRandom());
      HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
      } catch (Exception e) {
      e.printStackTrace();
      }
      }

      Thanks for your help.

    • Vito Andolini Says:

      Crazy Bob’s article is a solution for SSL issues with the HttpClient, not with a WebView. I don’t believe you can add a certificate to the app’s trusted store and have that used by a WebView.

  4. William Says:

    Hi,

    Just wanted to thank you to have resolved my problem in 2 seconds.

    Working on eclair 2.1 at the moment I had exactly the same issue you described. Your fixed avoided me a lot of troubles.

    thanks

    William

  5. Ansari Says:

    Thanks a lot, the above solution works.

  6. Sebastian B Says:

    Thank you very much, this is excellent!!!
    I had been struggling with this for some hours before I found your solution.


  7. Thanks, was having the same problem in a project targeting Android 1.6. Now I know what the heck is going on.


  8. [...] WebView just swallowed this with no error messages, but a bit of googling and found the following blog post about how to work round the certificate miss match and even how to make it work with Android [...]


  9. Hi All,
    I am trying to retrieve POST parameter from Response. but not able get it from android Webview.

    Could anyone help me or give an idea to retrieve POST data from response URL.

    Thanks & Regards

  10. Richard Says:

    I have applied your trick and it works fine until I run on a real Android device 1.6 or 2.1. Then the overloading of classes is stop by the DEX loader !!! any tricks to get around ?

    • Richard Says:

      08-15 12:03:41.938: DEBUG/dalvikvm(3003): DexOpt: ‘Landroid/net/http/SslError;’ has an earlier definition; blocking out
      08-15 12:03:41.938: DEBUG/dalvikvm(3003): DexOpt: ‘Landroid/webkit/WebViewClient;’ has an earlier definition; blocking out
      08-15 12:03:41.973: DEBUG/dalvikvm(3003): DexOpt: not verifying ‘Landroid/net/http/SslError;’: multiple definitions
      08-15 12:03:41.973: DEBUG/dalvikvm(3003): DexOpt: not verifying ‘Landroid/webkit/WebViewClient;’: multiple definitions
      08-15 12:03:42.153: INFO/dalvikvm(3003): DexOpt: not resolving ambiguous class ‘Landroid/webkit/WebViewClient;’

  11. tomozeron Says:

    Hi Damian,

    I’ve been struggling a couple of nights getting postUrl to work when posting to a https site. I tried to use all sorts of TrustedManagers etc etc but with no luck.
    Then I found this gem of yours, read it and in less than 10 minutes I was up’n running.

    I bow my head in humble submission.

    Thank you.

    /brian

  12. rpche Says:

    Hello, Damian thnx for the tutorial but i still dont manage to get it work. mayb if u have some time u can help me load https in my webview :)

    package here.testing.a.cool.app;

    import android.app.Activity;
    import android.os.Bundle;
    import android.view.Window;
    import android.webkit.WebChromeClient;
    import android.webkit.WebView;
    import android.webkit.WebViewClient;

    public class WebPageLoader extends Activity
    {
    final Activity activity = this;

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
    super.onCreate(savedInstanceState);
    this.getWindow().requestFeature(Window.FEATURE_PROGRESS);
    setContentView(R.layout.main);
    WebView webView = (WebView) findViewById(R.id.webview);
    webView.getSettings().setJavaScriptEnabled(true);

    webView.setWebChromeClient(new WebChromeClient() {
    public void onProgressChanged(WebView view, int progress)
    {
    activity.setTitle(“Grabbing the bits and the bytes..”);
    activity.setProgress(progress * 100);

    if(progress == 100)
    activity.setTitle(R.string.app_name);
    }
    });

    webView.setWebViewClient(new WebViewClient() {
    @Override
    public void onReceivedError(WebView view, int errorCode, String description, String failingUrl)
    {
    // Handle the error
    }

    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url)
    {
    view.loadUrl(url);
    return true;
    }
    });

    webView.loadUrl(“https://website.willnot.open”);
    }
    }

  13. Arthur Says:

    Thanks a lot!!!!

  14. becomingthebeast Says:

    Great post Damian.
    As always your articles are most useful.
    Thank you!


  15. [...] all http links are working fine, making a search i found this useful post. Where the author describes the problem, my min target sdk is froyo (api level 8) and [...]

  16. Saurabh Prajapati Says:

    Thank you very much to describe the option “d” to support for old Android APIs.


  17. [...] And also read this topic in blog Android WebView with https loadUrl shows blank/empty page [...]


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 47 other followers

%d bloggers like this: