Wifi STB Remote+ (JAVA) for laptop and desktops

My Android App Wifi STB Remote+, which works as a remote for TataSky+ HD Settop boxes, now has a laptop/desktop counterpart.

Wifi STB Remote+ JAVA - Main screen

Wifi STB Remote+ JAVA – Main screen

Wifi STB Remote+ JAVA - All channels screen

Wifi STB Remote+ JAVA – All channels screen

This application is built over Java and packaged as a Jar file. (Download link below.) So, Java (JRE 8 or JDK 8) needs to be installed on the computer where you want to run this.

Please note this is an experimental application. Unlike its Android counterpart this application is not that chatty and articulate about the issues it faces while making connection to the STB (Set Top Box).

To start this app usually just double clicking on it should work. If that doesn’t work then use then you need to open command prompt and use cd to goto the directory where you have kept this app then use the command – java -jar WifiStbRemotePlus.jar.

To use the app make sure your computer is connected to the same Wifi as the STB. Then click on “Connect Device”. When the connection is successful then you will notice a “Connected” message. If you keep on seeing “Connecting…” message for a long time then that means something is not right and it is unable to reach to the STB. In that case fix the issue and click “Connect Device” again. Note unlike the Android app this app does not let you choose the STB. It just finds the STB and when it finds it simply connects to it.

Download WifiStbRemotePlus jar file

Using pinned self-signed SSL certificate with Android Volley

Recently for one of my Android apps I wanted to use a self-signed certificate on the server-side. However, if you try to connect to such a server with default settings, the connection is going to be refused. This is because it has been signed by you (your server domain), and ‘you’ is not trusted by any system.

Read the excellent article – Android security – Implementation of Self-signed SSL certificate for your App. This describes all the concepts and the pros and cons of pinning. However, it does not describe how to do this in Volley. The internet is littered with many articles on this, and most of them are outdated and can’t be used now.

So, here is an updated guide. I have tested this on my app which uses Android API 22 and Volley code downloaded on Jan 2015.

First use the official guide to create a singleton class to get the request queue. Now below is the modified code which takes care of the SSL pinning.

public class MySingleton {
    private static char[] KEYSTORE_PASSWORD = "YourKeyStorePass".toCharArray();

    ...

    public RequestQueue getRequestQueue() {
        if (mRequestQueue == null) {
            // getApplicationContext() is key, it keeps you from leaking the
            // Activity or BroadcastReceiver if someone passes one in.
            mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext(), new HurlStack(null, newSslSocketFactory()));
        }
        return mRequestQueue;
    }

    ...

    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 = mCtx.getApplicationContext().getResources().openRawResource(R.raw.codeprojectssl);
            try {
                // Initialize the keystore with the provided trusted certificates
                // Provide the password of the keystore
                trusted.load(in, KEYSTORE_PASSWORD);
            } finally {
                in.close();
            }

            String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
            tmf.init(trusted);

            SSLContext context = SSLContext.getInstance("TLS");
            context.init(null, tmf.getTrustManagers(), null);

            SSLSocketFactory sf = context.getSocketFactory();
            return sf;
        } catch (Exception e) {
            throw new AssertionError(e);
        }
    }
}

Please note, that the above solution will work for Android API 9 and above. To support the below versions you need to pass an instance of HttpClientStack instead of HurlStack. So, replacing the line

mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext(), new HurlStack(null, newSslSocketFactory()));

with

mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext(), new HttpClientStack(new MyHttpClient()));

might work. Where MyHttpClient is the one defined in the CodeProject article. I haven’t tested this part though.

Lessons learned from PhoneGap (Cordova) and jQueryMobile on Android

I recently created my first Android app – JustTodo. This is a simple app but took considerable time to finish due many unexpected problems. Google wasn’t too helpful either. Here is a list of issues I faced and their solutions I discovered.

General tips

Zooming and Scaling

I presume that you have already encountered suggestions over the net that you should use meta tag in your html page. So, that your page is scaled to, maybe, take the exact device width. For example you may use…

<meta name="viewport" content="width=device-width, initial-scale=1.0">

width=device-width will make sure that your page’s width is equal to the device’s width. Of course, for any device, the width differs based on if it is in landscape or portrait orientation.

initial-scale=1 will hint the mobile browser to not zoom out the pages. Mobile browsers typically zoom out the whole page when it is initially loaded, so that the whole page fits on the screen. This way the user can tap anywhere to zoom into that exact location. You do not want that to happen to your apps.

However, in spite of the above setting, the browser may still scale your page. This is because a web page designed for iPhone 3G will look half its size on iPhone 5, since iPhone 5 has twice the pixel density. To prevent the webpages from breaking on high DPI devices, the devices usually scale them by a fixed percent to make them look like they would on MDPI (Medium Dots Per Inch) devices. Webpages can read the value of window.devicePixelRatio to know the factor by which they have been scaled. This is 1 for MDPI devices.

jQueryMobile tips

Do not minify jqm structure css!

I don’t know the reason or the root cause for this. When I use the jquery.mobile.structure css minified by Yahoo UI compressor, the fixed header stops resizing on orientation change! The solution is simple. Always use the official minified version.

tap event gets fired when swipe is fired

If on an element if you are listenning for both tap and swipe events, then it would be better to replace tap by vclick. This is because tap is fired before swipe. However, in case of vclick, it waits for about 200 – 300ms to check if any other event is fired. If not then it fires that. This way you can prevent user accidentally clicking an element while trying to swipe it.

Better swipe event detection

Jqm sets swipe horizontal distance threshold to 30px and time threshold to 1s. Which means to successfully swipe an element the user needs to drag his finger at least 30px horizontally within 1s. I usually set the time threshold to 2.5s. However, due to scaling on high density bigger phones and tablets the physical distance covered by 30px on a MDPI and a XHDPI might vary by a lot. This would force the users to drag their fingers for longer distance in the same duration. So, the trick is to change the distance threshold, such that it covers the same physical distance on all devices.

I wrote the following Javascript function for that.

#!javascript
function getLenInCurrDevice(len) {
    var refernecDPI = 192.2960680247461, // Ref device is Sony Xperia Mini Pro - SK17i.
        pRatio = window.devicePixelRatio, // Ratio of current device DPI on a square inch to that of a MDPI.
        currDPI = refernecDPI * Math.sqrt(pRatio),
        originalWInInch = len / refernecDPI;
    return (originalWInInch / pRatio) * currDPI;
}

For a given distance in px the above function will return a new distance in px on the current device, such that, the distances cover the same physcial length on the current and the reference devices. In this case the reference device is Sony Xperia Mini Pro Sk17i, which has DPI of about 192.3 and devicePixelRatio of 1.

If you want to accurately calculate the DPI of your device you can use the DPI Calculator here.

Cordova tips

Caution when using Proguard

Build files created for an Android project which allows you to enable Proguard on your project. Proguard analysis the Java files and removes all which are not used. However, it also strips out the Cordova plugin classes, since it does see them referenced in any Java classes. (They are referenced from the cordova.js file.) So, you need to add the following to your Proguard config.

-keep class org.apache.cordova.** { *; }

Minimum plugins needed

This is not documented anywhere and it seems the minimum plugins needed are – App and Device. Frankly, I did not try removing them ever. So, maybe even they too are needed. Just try it and let me know. 😉

Although I must mention that if you remove NetworkStatus plugin then occasionally you might see error related to that in the console. Other than that there is no adverse effect of that. In my app I have kept this disabled, so that I can create an app which requires no special permissions. 🙂

Remove junk files and folders

Be sure to delete assets/www/res, assets/www/specs and assets/www/specs.html files and folders. The first one might be about 7MB! Actually the only file needed is cordova.js and the cordova.jar file.

Show native Android context menu

In JustTodo the user when long presses an item in the list it programmatically shows the context menu from the JS. There are two parts to this problem. First is adding the code which allows the JS to open the context menu. Second is to prevent WebView from automatically opening the context menu. More on this later.

Implementing context menu

First step is creating the context menu xml. Below is an example.

res/menu/example_ctx_menu.xml. The xml’s name can be of your choosing.

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
    <item android:id="@+id/edit"
          android:title="@string/edit"/>
    <item android:id="@+id/delete"
          android:title="@string/delete" />    
</menu>

res/values/strings.xml. This maps the key we used in menu xml to the actual string which is displayed to the user.

<?xml version='1.0' encoding='utf-8'?>
<resources>
    <string name="edit">Edit</string>
    <string name="delete">Delete</string>
</resources>

The official way we should be implementing this is using Cordova Plugins. However, I find the technique described here to be simpler. You be your best judge.

NativeContextMenu.java

#!java
public class NativeContextMenu {
    private WebView mAppView;
    private DroidGap mGap;

    public NativeContextMenu(DroidGap gap, WebView view) {
      mAppView = view;
      mGap = gap;
      mGap.registerForContextMenu(mAppView);
    }

    @JavascriptInterface
    public void showCtxMenu() {
        mGap.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mGap.openContextMenu(mAppView);
            }
        });
    }

    private void raiseJSEvent(String event) {
        mGap.sendJavascript("$(document).trigger('" + event + "');");
    }

    boolean onContextItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.edit:
            raiseJSEvent("menu.item.edit");
            return true;
        case R.id.delete:
            raiseJSEvent("menu.item.delete");
            return true;
        }
        return false;
    }

    void onCreateContextMenu(ContextMenu menu, View v,
                                    ContextMenuInfo menuInfo) {
        mGap.getMenuInflater().inflate(R.menu.todo_ctxmenu, menu);
        raiseJSEvent("menu.opened");
    }

    void onContextMenuClosed(Menu menu) {
        raiseJSEvent("menu.closed");
    }
}

YourCordovaActivity.java

#!java
public class YourCordovaActivity extends DroidGap {
    private NativeContextMenu ctxMenu;

    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        super.loadUrl(Config.getStartUrl());

        ctxMenu = new NativeContextMenu(this, appView);
        appView.addJavascriptInterface(ctxMenu, "ContextMenu");
    }

    @Override
    public boolean onContextItemSelected(MenuItem item) {
        return ctxMenu.onContextItemSelected(item) ? true : super.onContextItemSelected(item);
    }

    @Override
    public void onContextMenuClosed(Menu menu) {
        super.onContextMenuClosed(menu);
        ctxMenu.onContextMenuClosed(menu);
    }

    @Override
    public void onCreateContextMenu(ContextMenu menu, View v,
                                    ContextMenuInfo menuInfo) {
        super.onCreateContextMenu(menu, v, menuInfo);
        ctxMenu.onCreateContextMenu(menu, v, menuInfo);
    }
}

Now ContextMenu.showCtxMenu() would be available to you in Javascript.

example.js

#!javascript
$('element').on('taphold', function  () { // taphold event is defined by jqm
    ContextMenu.showCtxMenu(); // Shows the context menu.
                               // Also the user will get a haptic feedback.
});

$(document).on('menu.item.edit', function () {
    console.log('Edit option was selected.');    
});

Preventing WebView from automatically opening the context menu

The big problem you will face here that when you long press, the context menu will open twice. One by your call in JS code, and another by WebView. WebView has a method setLongClickable() which even if you set after calling registerForContextMenu() does not seem to have any effect. WebView directly calls performLongClick() without checking if isLongClickable(). So the other way to do this is make NativeContextMenu also implement OnLongClickListener.

Changed codes.

NativeContextMenu.java

#!java
public class NativeContextMenu implements OnLongClickListener {  // <---
    private WebView mAppView;
    private DroidGap mGap;

    public NativeContextMenu(DroidGap gap, WebView view) {
      mAppView = view;
      mGap = gap;
      mGap.registerForContextMenu(mAppView);
      mAppView.setOnLongClickListener(this); // <---
    }

    @JavascriptInterface
    public void showCtxMenu() {
        mGap.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mGap.openContextMenu(mAppView);
            }
        });
    }

    private void raiseJSEvent(String event) {
        mGap.sendJavascript("$(document).trigger('" + event + "');");
    }

    boolean onContextItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.edit:
            raiseJSEvent("menu.item.edit");
            return true;
        case R.id.delete:
            raiseJSEvent("menu.item.delete");
            return true;
        }
        return false;
    }

    void onCreateContextMenu(ContextMenu menu, View v,
                                    ContextMenuInfo menuInfo) {
        mGap.getMenuInflater().inflate(R.menu.todo_ctxmenu, menu);
        raiseJSEvent("menu.opened");
    }

    void onContextMenuClosed(Menu menu) {
        raiseJSEvent("menu.closed");
    }

    @Override
    public boolean onLongClick(View v) {  //<---
        return true; // We return true, to let performLongClick() know that we handled the long press.
    }
}

The only side effect of the above code is that whenever the user long presses and you do not show a context menu, the user will still get the haptic feedback. The only way to circumvent that is by sub-classing CordovaWebView and overriding performLongClick().

Google really finally allows India to sell Android apps

There were reports a month back I suppose when Google mistakenly added India to the list of approved countries. Later that they removed India from the list, apologizing for the mistake.

Now finally all India Android devs can rejoice. India is finally in the list. I just enrolled today. Check the below official link to confirm.

Supported locations for merchants – Google Play for Developers Help.