Wednesday, October 17, 2012

Android protip: remove debug logs from release builds with Proguard

I use the android.util.Log extensively - often it's faster than starting the debugger and (unlike debugging) it's always on, which is invaluable when you're trying to track the root cause of some hard to reproduce bugs. Logging is also nice for release candidate builds you give to the QA team - if they find some bugs you'll have more information than just a stack trace.

On the other hand you don't want to keep all those logs for release builds, mostly for performance and privacy reasons. If you google around  for a solution to this issue you'll probably find a dozen ideas like using a wrapper for logging class, using a dedicated logging framework or even some sed/awk scripts to ant build process. Among those ideas is the one I'd like to recommend: customizing Proguard configuration.

Proguard is automatically run in release builds by ant and Export wizards from Eclipse and there is an optimization option, which can be used to disable logs:

-assumenosideeffects
Specifies methods that don't have any side effects (other than maybe returning a value). In the optimization step, ProGuard will then remove calls to such methods, if it can determine that the return values aren't used.
I wouldn't use this tool for the purpose stated in the manual, because even if my code doesn't have any side effects, the methods called from it might have ones; I'd rather use some static code analysis tool to find the unnecessary calls and manually remove them. It looks perfect for suppressing the logs though and it's very simple to set up - just add the following lines to the proguard-project.txt file generated by android command line tool:

-assumenosideeffects class android.util.Log {
    public static *** d(...);
    public static *** v(...);
    public static *** i(...);
    public static *** w(...);
    public static *** e(...);
}

You can of course keep some of the priority levels by removing lines from this config.

The nice thing about this solution is that you have to set it only once and it will just work for all further release builds.

UPDATE (August 2013): do not assume that Proguard will remove any code other than the methods listed in assumenosideeffects setting. See "Proguard gotcha" post for more details.

Tuesday, October 9, 2012

Android font metrics for dummies

Recently I was working on a custom View with overridden onDraw method and at some point I needed to center some text vertically. Unfortunately Paint.setTextAlign() supports only horizontal orientation, so I tried calculating the y-coordinate of the origin myself, but I couldn't get it exactly right. After two failed attempts I decided that I need a program which visualizes the available font metrics, because it seems that I do not understand the FontMetrics documentation, or the aforementioned class and it's documentation sucks.

You can find the source code on my GitHub, and here's the screenshot for other typographically challenged programmers:



(BTW: the word "Żyłę" used there is not a complete gibberish, it's an accusative case of word "vein" in Polish; it's nice, because it's short, but it covers all cases of the metrics class).

Let's get back to vertical alignment. In general you should center text vertically either on x-height or on half the cap height above the baseline (at least that's the info I found). Neither metric is directly available in FontMetrics class, but you can approximate the cap height as a (textSize - descent) or calculate x-height yourself using Rect height returned by Paint.getTextBounds for string "x".

Wednesday, October 3, 2012

Screensaver blocking on Symbian

In my latest game the players spend significant amount of time just watching the screen and trying to figure the puzzle out. The first few levels are obvious and most players will sole them in a few seconds,m but as the difficulty of the puzzles increases the players stare at the screen longer and longer. At some point the screensaver would kick in and piss the player off (fortunately my ragtag QA/betatester team found this issue before launch).

Google search results are (as usual) helpful, but the most promising lead, the QSystemScreenSaver class is not a solution. There are three problems with it:

  1. The API of the class itself is terrible.
  2. The API of the related QML element is even worse.
  3. Last, but not least, it doesn't work (at least not in the Qt Mobility version shipped with the Qt SDK).

(BTW: these three points sums up pretty much every experience with Qt Mobility package I had. Qt devs should either kill this festering boil with fire or fix it and rename it, because I learned to dread everything remotely related to Qt Mobility, and I suspect I'm not the only one).

Anyways, let's get back to the core of the problem, i.e. "how to block the screensaver". Qt Mobility failed, but the task doesn't seems like a rocket science to me. Slightly different Google search suggested using native Symbian's User::ResetInactivityTime() method. Few minutes and one QTimer later, everything worked:
#ifndef SCREENSAVERBLOCKER_H
#define SCREENSAVERBLOCKER_H

#include <QObject>
#include <QApplication>
#include <QTimer>

class ScreenSaverBlocker : public QObject
{
    Q_OBJECT

public:
    explicit ScreenSaverBlocker(QObject *parent = 0) : QObject(parent) {
        mTimer.setInterval(1000);
        connect(&mTimer, SIGNAL(timeout()), this, SLOT(blockScreenSaver()));
        changeScreenSaverState(QApplication::activeWindow() != 0);
        if (qApp) {
            qApp->installEventFilter(this);
        }
    }

    void changeScreenSaverState(bool blockScreenSaver) {
        if (blockScreenSaver && !mTimer.isActive()) {
            mTimer.start();
        } else {
            mTimer.stop();
        }
    }

protected:
    bool eventFilter(QObject *obj, QEvent *event) {
        Q_UNUSED(obj)
        if (event->type() == QEvent::ApplicationActivate
         || event->type() == QEvent::ApplicationDeactivate) {
            changeScreenSaverState(event->type() == QEvent::ApplicationActivate);
        }
        return false;
    }

private slots:
    void blockScreenSaver() {
#ifdef Q_OS_SYMBIAN
        User::ResetInactivityTime();
#endif
    }

private:
    QTimer mTimer;
};

#endif // SCREENSAVERBLOCKER_H
The important thing in the code above is watching the ApplicationActivate and ApplicationDeactivate events - after all, when your app is in background, you shouldn't affect the phone behavior. I'm not sure if the app would fail the Nokia's QA process without this feature, but it seemed prudent to write the code this way.

If you want to use this object in your QML UI just register it with qmlRegisterType and add the registered import and QML element to your root element.