Menestrello is a hybrid app built (locally) with PhoneGap.

This means that the UI and most of the logic is written in HTML5 and Javascript code, shared by both the iOS and Android versions. Low-level functions are written in native code (Objective-C for iOS and Java for Android), and, in Menestrello, they manage:

  • discovering or importing the EPUB files;
  • unzipping the assets inside the EPUB file;
  • reading EPUB metadata from the OPF manifest;
  • managing the audio output.

The "glue" between the Javascript and the native part are the so called "PhoneGap plugins", which provide a mechanism to pass data and commands between the two.

Menestrello has a "night mode", which blackens the UI chrome. The Android version looks like the following screenshot:

Blog Image menestrello-night-1.png

However, the status bar remains lighted up and it is very distracting if you read in total dark. Yesterday, I decided to fix this issue.

First of all, I found out that the following line, placed in the onCreate method of the app main activity, does the trick:

getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);

Unfortunately, this solution has several shortcomings:

  • it works only on Android 4.0 (ICE_CREAM_SANDWICH) and later (alright, you cannot get around this limitation);
  • the status bar returns to normal status (lit) if the user taps it, and never dims again; and
  • I could not control it from the Javascript part of the app (so it would be dimmed also for the "day" theme).

So, I decided to code a PhoneGap plugin to dim (or light up) the status bar from Javascript, as needed by the app logic.

It proved quite tricky. I cannot share the final code of my plugin (as it is work I have done for my company ReadBeyond, even thought very outside office hours...) but I want to spare you the painful trial-and-error I went through, mostly due to poor PhoneGap documentation.

There are two key fact you need to know.

Run on UI thread

If want your PhoneGap plugin to interact with the app UI (which runs on a different thread), you need to explicitly ask it with runOnUiThread so:

cordova.getActivity().runOnUiThread(new Runnable() {
    public void run() {
        // your code goes here
    }
});

I already knew about this issue, but in case you do not know it, it is quite easy to find out, as you will get a runtime exception, pointing you to runOnUiThread.

Get the right View

The very subtle part was actually realizing that my initial code:

cordova.getActivity().runOnUiThread(new Runnable() {
    public void run() {
        // not working!
        View rootView = cordova.getActivity().getWindow().getDecorView();
        rootView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
    }
});

was not doing anything because it was acting (silently) on the wrong View!

The correct code is:

cordova.getActivity().runOnUiThread(new Runnable() {
    public void run() {
        // working!
        View rootView = cordova.getActivity().getWindow().getDecorView().getRootView();
        rootView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE); // dim
        // rootView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); // light up
    }
});

Note the getRootView() call: apparently, PhoneGap creates a hierarchy of View objects and you must act on its root to dim or light up the status bar of your app.

The final result, which will ship in Menestrello 2.7.0, the upcoming next release:

Blog Image menestrello-night-2.png

Yay! The status bar is dimmed, and it will be dimmed/lighted up as needed, depending on user interaction and (Javascript) logic.

In a future version of Menestrello we will probably include a user-defined behavior to hide the status bar altogether, when in Reader View.