Canalblog
Suivre ce blog Administration + Créer mon blog

It was not (so) obvious

31 octobre 2014

Connecting with https to a url using a self signed certificate

Something good to know when for instance you have to connect to a QA backend that doesn't have a valid certificate. Here's how to proceed:

1st step: download bouncycastle

Android uses Bouncycastle to handle ssl certificates. It uses version 1.46 Keystores generated with later versions won't work for Android and will cause exceptions like java.io.IOException: Wrong version of key store on runtime 

Download Bouncycastle

2nd step: get the certificate of the site

There are different ways to achieve this. Using openssl in command line for instance or opening the url in a browser, checking the certificate details and saving the certificate to a file (*.cer)

3rd step: generate a keystore

This can be done using the keytool program found in your JDK. Use the following command:

keytool -import -v -trustcacerts -alias 0 -file [full certificate name] -keystore [keystore file name].bks -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerPath [path to Jar file]/bcprov-jdk15on-146.jar -storepass [password]

A bks file should be created after this step

4th step: copy the keystore to your Android project

The file should be copied to resources/raw

5th step: add the following class to your project

public class MyHttpsClient extends DefaultHttpClient {
    final Context context;
    
    public MyHttpsClient(Context context) {
        this.context = context;
    }
  
    @Override
    protected ClientConnectionManager createClientConnectionManager() {
        SchemeRegistry registry = new SchemeRegistry();
        registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
        // Register for port 443 our SSLSocketFactory with our keystore
        // to the ConnectionManager
        registry.register(new Scheme("https", newSslSocketFactory(), 443));
        return new SingleClientConnManager(getParams(), registry);
    }
  
    private SSLSocketFactory newSslSocketFactory() {
        try {
            // Get an instance of the Bouncy Castle KeyStore format
            KeyStore trusted = KeyStore.getInstance("BKS");
            // Get the raw resource, which contains the keystore with
            // your trusted certificates (root and any intermediate certs)
            InputStream in = context.getResources().openRawResource(R.raw.keystore);
            try {
                // Initialize the keystore with the provided trusted certificates
                // Also provide the password of the keystore
                trusted.load(in, "password".toCharArray());
            } finally {
                in.close();
            }
            // Pass the keystore to the SSLSocketFactory. The factory is responsible
            // for the verification of the server certificate.
            SSLSocketFactory sf = new SSLSocketFactory(trusted);
            // Hostname verification from certificate
            // http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d4e506
            sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
            return sf;
        } catch (Exception e) {
            throw new AssertionError(e);
        }
    }
}

Remember to update the password and change the resource name if necessary.

6th step: Call your new Https client from your code

defaultHttpClient client = new MyHttpClient(getApplicationContext());

Post inspired by:
http://blog.antoine.li/2010/10/22/android-trusting-ssl-certificates/
http://blog.crazybob.org/2010/02/android-trusting-ssl-certificates.html
http://stackoverflow.com/questions/11963852/wrong-version-of-key-store-error-how-can-i-create-a-version-1-keystore-certif


Publicité
Publicité
6 mars 2014

Apps based on webviews

In order to save time and money, you might be tempted to build a website, then wrap it inside a native app. The idea is to get the best of the 2 worlds: pages running on any platform (with HTML 5 you can do almost anything you'd do natively) and native features that you can't get even with HTML 5 such as push notifications.

Easier to tell than to do though. You might face different problems with webviews:

Cache manifest

A cool feature that should allow your users to use the app even offline. Not so obvious to make it work though:

On Windows Phone

On Android

  • You'll have to configure the webview so that it can cache data. For this you can use

        WebSettings webSettings = webview.getSettings();
        webSettings.setJavaScriptEnabled(true);
        webSettings.setDomStorageEnabled(true);

        if (activeNetwork != null && activeNetwork.isConnected()) {
            webSettings.setAppCacheMaxSize(1024*1024*32);
            webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
        }
        else{
            webSettings.setCacheMode(WebSettings.LOAD_CACHE_ONLY);
        }
        String appCachePath = getApplicationContext().getCacheDir().getAbsolutePath();
        webSettings.setAppCachePath(appCachePath);
        webSettings.setAllowFileAccess(true);
        webSettings.setAppCacheEnabled(true);

  • Note that setAppCacheMaxSize has been deprecated. The javadoc tells This method was deprecated in API level 18. In future quota will be managed automatically. Apparently future is not now yet and this seems to be still required.
  • You might also want to override a WebChromeClient method like so

    private class CustomChromeClient extends WebChromeClient{

          @Override
          public void onReachedMaxAppCacheSize(long spaceNeeded, long totalUsedQuota,
                       WebStorage.QuotaUpdater quotaUpdater)
          {
                quotaUpdater.updateQuota(spaceNeeded * 2);
          }

    }

And assign this ChormeClient to your webview: webview.setWebChromeClient(new CustomChromeClient());

Bridge between Javascript and the native app

You might want your native code to talk with the Javascript of your website. Unfortunately, every platform addresses this problem in a different way

On Android

  • Javascript to Java

You'll need to create a new Java class (let's say AndroidBridge) containing the methods you want to be able to call from Java and use the @JavascriptInterface annotation on them. For instance:

    @JavascriptInterface
    public void clearEvent(){
        activity.saveCurrentEvent(null);
    }

Then add this class as a javascript interface to your webview

    webview.addJavascriptInterface(new AndroidBridge(this), "AndroidBridge");

You can then call these methods from Javascript like so:

    if(window.AndroidBridge){
        window.AndroidBridge.clearEvent();
    }

You can of course give parameters to the method.

  • Java to Javascript

Use WebView.loadUrl with javascript as the parameter. For instance webview.loadUrl("javascript:(function(){goback();})()");

On Windows Phone

  • Javascript to C#

On the C#, you have to use the Browser.ScriptNotify event handler. Assign a method to it and there you go.

    Browser.ScriptNotify += new EventHandler(Browser_ScriptNotify);

    //Javascript interface
    void Browser_ScriptNotify(object sender, NotifyEventArgs e)
    {
        String[] parameters = e.Value.Split(new Char[] {':'});
        ...
    }

On the Javascript side, use window.external.notify (be careful though as Firefox also uses window.external and you don't want code for WP to execute on Firefox):

    else if(window.external && 'notify' in window.external){ //Firefox uses window.external for other purposes
        window.external.notify("subscribePushNotifications:" + event);
    }

Note that you can give only one parameter, so if you have to transmit several variables, concatenate them on the Javascript side using a separator and split it on the C# side

  • C# to Javascript

Simply use WebBrowser.InvokeScript, for instance Browser.InvokeScript("goback");

On iOS

There's no native support for Javascript/Objective C communication but there are workarounds. For my project I used a bridge created by Marcus Westin that you can find here:

https://github.com/marcuswestin/WebViewJavascriptBridge

Follow the instructions there and it should work just fine.

Bonus: Setting up the webview in Android

It's not that obvious to make your android webview act as if it was a browser. Here's the piece of code I used for my own project. You might want to adapt it to your own needs.

        webview = (WebView) findViewById(R.id.webview);
        webview.setWebViewClient(new WebViewClient());
        webview.setWebChromeClient(new WebChromeClient());
        webview = (WebView) findViewById(R.id.webview);

     final ConnectivityManager conMgr =  (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        final NetworkInfo activeNetwork = conMgr.getActiveNetworkInfo();
                
        WebSettings webSettings = webview.getSettings();
        webSettings.setJavaScriptEnabled(true);
        webSettings.setLoadWithOverviewMode(true);
        webSettings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL);
        webSettings.setDefaultZoom(ZoomDensity.MEDIUM);
        webSettings.setSupportZoom(true);
        webSettings.setBuiltInZoomControls(true);
        webSettings.setUseWideViewPort(true);
        webSettings.setDomStorageEnabled(true);
        if (activeNetwork != null && activeNetwork.isConnected()) {
            webSettings.setAppCacheMaxSize(1024*1024*32);
            webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
        }
        else{
            webSettings.setCacheMode(WebSettings.LOAD_CACHE_ONLY);
        }
        String appCachePath = getApplicationContext().getCacheDir().getAbsolutePath();
        webSettings.setAppCachePath(appCachePath);
        webSettings.setAllowFileAccess(true);
        webSettings.setAppCacheEnabled(true);
        
        
        webview.setScrollBarStyle(WebView.SCROLLBARS_OUTSIDE_OVERLAY);

Bonus: history.back() on iOS7

It simply doesn't work on iOS7. It also means that data-rel="back" from jQueryMobile won't work on these devices. Yay!

29 août 2013

Change the timezone of a date in Java

Sometimes you have to parse data containing dates that don't specify the timezone. It is implied that the timezone is the one of the user who inputs the data. Unfortunately, the computer doesn't really get it.

To solve this problem, I format the date and then parse it again using the SimpleDateFormat

        //Format the date which doesn't have the timezone set
        SimpleDateFormat parserSDF= new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss",Locale.ENGLISH);
        String dateWithoutTimeZone = parserSDF.format(date);
        //Specify the timezone and parse the date
        parserSDF.setTimeZone(timezone);
        Date dDate = parserSDF.parse(dateWithoutTimeZone);

7 juin 2013

Offline webapp on IPad

A weird difference between iPad and iPhone regarding the HTML cache manifest

Making a long story short: the absolute URLs do not work in the cache manifest on IPad (iOS version 5.1.1) even if they work on iPhone. One of the consequences is that you should have all the necessary files on your server if you want your web app to work offline on iPad.

Some useful links about this:

 

 

 

26 avril 2013

Installing the Facebook SDK for Visual Studio

Installing the Facebook SDK for Android on Eclipse was pretty straightforward. I hoped it would be the same for Windows Phone...

Facebook tutorial page:

http://facebooksdk.net/docs/phone/tutorial/

Problems & solutions:

Now on with the Facebook development!

Publicité
Publicité
It was not (so) obvious
Publicité
Archives
Publicité