Dev001

Geek Blog

PhoneGap/Cordova provides the backbutton event which is called when the users hits the Back button of his/her Android device.

If you use NavigationView to provide navigation for your Sencha Touch application, you might want to integrate the back button with it (so that pop()) is called when the back button is pressed.

However, you will notice that there’s a Sencha Touch bug that causes the NavigationView to react abnormally when you hit the Back button (either the Android one or the one in the navigation bar) more than once – the pop() method is called while the animation of the previous pop() is still active, causing the wrong view to be displayed and disorder in the navigation hierarchy. So I have decided to override the back button listener with the same method, but single: true. As soon as the pop() is complete, this listener is added again (see config/control/nav/pop).

    var nav = Ext.define('MyApp.controller.Navigation', {
        extend: 'Ext.app.Controller',

        config: {
            refs: {
                nav: 'navigation'
            },
            control: {
                nav: {
                    pop: 'addBackButtonListener',
                    initialize: 'initialize'
                }
            }
        },

        initialize: function() {
            var viewport = this;

            // listen to hardware back button
            document.addEventListener("backbutton", function() {
                var nav = viewport.getNav();
                if (nav.getInnerItems().length > 1)
                    nav.getNavigationBar().fireEvent('back');
                else
                    navigator.app.exitApp();
            }, false);

            // HACK: override back button listener
            this.getNav().getNavigationBar().removeListener('back', nav.onBackButtonTap, nav);
            this.addBackButtonListener();
        },

        addBackButtonListener: function() {
            var nav = this.getNav();
            var navBar = nav.getNavigationBar();
            navBar.on({
                back: nav.onBackButtonTap,
                scope: nav,
                single: true
            });
        }
    });

Previously, it was quite cumbersome to import a self-signed certificate (or the root certificate of your own / your organization’s CA): you had to export it in the correct format on your PC, transfer the file to the Android device and then import it.

Therefore, we have created the Android app CAdroid. It does these tasks automatically:

  1. Open the app and click “Next” when you have read the intro page.
  2. Enter the host name / port of your SSL/TLS site. CAdroid will fetch the certificate.
  3. After you have verified the certificate, it will be exported to your “external storage” in the correct format.
  4. Then you can import it with a few clicks. Instructions for this will be shown.
Asker Anonymous Asks:
Hallo rfc2822! Als Nutzer von DAVdroid bin ich gerade über ein ähnliches OpenSource-Projekt gestolpert: Flock (ebenfalls bei GitHub). Dort wird auch ein Android-Sync-Adapter implementiert, der CardDAV und CalDAV nutzt, aber zusätzlich noch die Einträge irgendwie verschlüsselt. Auf den ersten Blick scheint mir DAVdroid zwar deutlich umfangreicher, aber vielleicht kannst du ja dort -- im Sinne des OpenSource-Gedanken -- etwas für dein DAVdroid-Projekt übernehmen oder abgucken.
rfc2822 rfc2822 Said:

Allzuviel wird man dort nicht abgucken können, da mit Flock die harte Arbeit von DAVdroid mehr oder weniger übernommen (siehe Quellcode-Vergleich, man merkt es eindeutig und es ist dort sogar erwähnt), mit ein bisschen zusätzlicher GUI versehen und gratis auf Play Store gestellt wurde. Dazu wird ein eigener (so viel ich gesehen habe proprietärer, kostenpflichtiger) Serverdienst beworben. Ich konnte von “Verschlüsselung” auch nichts entdecken, was bei einem CalDAV/CardDAV-Client ja auch keinen Sinn macht. Fundierte Feature-Requests diesbezüglich aber bitte gerne in die DAVdroid-Issue auf Github eingeben.

PC 1 (AMD)

  • AMD Phenom(tm) II X4 965 Processor
  • nVidia GTX 570
  • 2 x SSD + 1 x HDD (Linux), 1x HDD (Windows)

Linux (Fedora 20):

  • 100 W idle (non-graphical console)
  • 100 W idle (Gnome 3 desktop)
  • 150 W – 220 W while gnome-tracker rages around uselessly
  • 180 W full CPU load

Windows:

  • 125 W idle
  • 350 W full load gaming (BF4)

PC 2 (Intel)

  • Intel i7 4790K
  • nVidia GTX 770
  • 1x SSD (Linux/Windows), 1x HDD (Linux)

Linux:

  • 64 W idle (non-graphical console)
  • 100 W idle (Gnome 3 desktop)
  • 168 W full CPU load

Windows:

  • 60 W idle
  • 310 W full load gaming (BF4)

Assuming you have created a Sencha Touch project with Sencha Cmd, you will see a directory structure like this:

    (root)                           
    ├── app                 # your Sencha Touch code goes here
    ├── app.json            # application info for Sencha Cmd
    ├── app.js              # (removable, generated by Sencha Cmd)
    ├── bootstrap.js        # (removable, generated by Sencha Cmd)
    ├── bootstrap.json      # (removable, generated by Sencha Cmd)
    ├── build               # output of "sencha app build" goes here for all non-Cordova environments,
    │                       # i.e. for "production", "testing" and "package", but not for "native"
    ├── build.xml           # ant script for building, used by Sencha; minimal script only includes
    │                       # .sencha/app/build.xml files; build process can be adapted here
    ├── config.xml          # project properties used by Sencha build process
    ├── cordova             # root of the Cordova project; you may use the Cordova CLI here
    │   ├── merges          # allows adding platform-specific files to www/
    │   ├── platforms       # platform-specific project (managed and generated by Cordova)
    │   ├── plugins         # Cordova plugins (e.g. org.apache.cordova.splashscreen)
    │   └── www             # output of "sencha app build native" goes here
    ├── index.html          # index page for Sencha Touch
    ├── cordova.local.properties    # "sencha app build native" will build packages for the platforms listed here
    ├── packager.json       # settings for creating the native packages (e.g. required app permissions)
    ├── resources           # resources, build process will always copy them into build/… or cordova/www/resources
    │   ├── css             # stylesheets (generated by compass from sass/)
    │   ├── icons           # icons
    │   ├── images          # images
    │   ├── sass            # stylesheet sources (SASS)
    └── touch               # Sencha Touch source code

So, for a minimal git repository, I use this .gitignore file:

    # framework
    .sencha/
    touch/

    # generated files
    app.js
    bootstrap.js
    bootstrap.json
    build/
    resources/css/
    resources/sass/.sass-cache/

    cordova/

To initialize a local repository of this app:

git clone myapp
sencha -sdk /path/to/sencha/sdk generate app MyApp myapp
# handle a few merge conflicts (use .$old files)

sencha cordova init com.xxx.myapp

# test building without Cordova:
sencha app build testing

# set platforms in cordova.local.properties
# build Cordova native packages:
sencha app build native

I still don’t know how to add platform-specific files, for instance files to Android res/. I guess the proper way will be hooks.

Deploying a Rails project is not a simple task when done right. In this article, I’ll show you my setup.

Prerequisites:

  • one FreeBSD/10 host (app server, Web server and database server at once)
  • a Ruby on Rails (3.x or 4.x, at the moment) project that runs fine in development mode (it’s named “myproject” here)
  • nginx Web server (works with others, too)
  • sudo must be available (install from security/sudo port), the reason will be explained later

In this example, I’ll use puma, but the procedure is the same for Unicorn or whatever server you prefer:

# Gemfile
# [...]
# application  server
gem 'puma'

group :development do
  # deploy with Capistrano
  gem 'capistrano-rails'
  gem 'capistrano3-puma'   # for puma:* Capistrano recipes
  gem 'highline'
end

Basic steps

This is the desired directory structure:

/srv/www/myproject/devel/           # development version, belongs to developer
/srv/www/myproject/live/            # live version, belongs to deployment system user
/srv/www/myproject/live/releases/   # recent releases of the live version
/srv/www/myproject/live/current/    # current release of the live version (symlink)
/srv/www/myproject/live/shared/     # files shared by the live version releases

So, what we need to do?

  1. Prepare the system user for the deployed app as well as the deployment directory and permissions.
  2. Capify the Rails project, set up Capistrano to deploy correctly and test the whole thing.
  3. Use the capistrano3-puma recipes to manage Puma processes (start, stop, status, restart). You will also need a script that starts the Puma server when the machine is rebooted. You may use a process monitoring service like Bluepill, Monit or God, but because of my bad experiences with God (crashes etc.), I don’t use it anymore. One less “tool” also means one less error source.

Preparing the deployment environment

The deployed application shall run as a new non-privileged system user (called “application user” from now on) for security reasons, so let’s create a new system user:

pw useradd myproject -m -c "MyProject Web application user" -d /srv/www/myproject/live -s /bin/sh`

Then we create the deployment directory and assign it to the new user:

mkdir /srv/www/myproject/live/
chown myproject:myproject /srv/www/myproject/live/

Capistrano deploys via SSH because it’s made to deploy to multiple servers. In our case, we have only one server, but Capistrano will still connect via SSH: the deploying user (the one who calls cap deploy) will ssh to the server being deployed to (in our case, our one and only server) as the application user (myproject in our case): ssh myproject@localhost. To avoid unnecessary passwords, set up SSH login with keys (the keys go into the project’s home directory /srv/www/myproject/live) and verify it’s working (deploying_user$ ssh myproject@localhost should connect and leave you at a /bin/sh prompt, after optionally asking for your passphrase, but not a password). You may also want to inform you on how to use ssh-agent, so that you don’t have to enter your passphrase for every deployment command later.

Now Capistrano is able to connect to the server (localhost) via SSH as the application user, change the working directory to to /srv/www/myproject/live/, download the most recent application source from the git repository and do all the other necessary things (create symlinks, compile assets, run tests, etc.). Make sure the directory belongs to the application user, otherwise this won’t work.

Capifying the Rails project

If you’re upgrading a project from Capistrano 2, it’s recommended to delete your old Capfile and config/deploy* and start from scratch.

After initializing the Capistrano files using

bundle exec cap install

a new Capfile will be created. Adapt it to your needs. In my case, it looks like this (without comments):

require 'capistrano/setup'
require 'capistrano/deploy'

# require 'capistrano/rvm'
# require 'capistrano/rbenv'
# require 'capistrano/chruby'
require 'capistrano/bundler'
require 'capistrano/puma'
require 'capistrano/rails/assets'
require 'capistrano/rails/migrations'

Dir.glob('lib/capistrano/tasks/*.cap').each { |r| import r }

I don’t use rvm, rbenv or chruby but only Ruby 2.1 from the ports (http://www.freshports.org/lang/ruby21). The other includes provide Capistrano tasks related to Bundler, Rails and Puma.

Now, edit config/deploy.rb. Here are the most important settings:

set :application, 'MyProject'
set :repo_url, '/home/git-repos/myproject.git'

# these directories will be shared between releases
# tmp/pids will be required for watching/killing puma
# tmp/cache should persist for performance reasons
# tmp/sockets is required for providing a socket for nginx
# public/system contains uploaded files etc. which shall of course persist between releases
set :linked_dirs, %w{log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system}

namespace :deploy do
  # the :start, :stop, :restart recipes are managed by capistrano-puma
  # and call the respectiva puma: recipes

  after :publishing, :restart

  after :restart, :clear_cache do
    # Here we can do anything such as:
    # within release_path do
    #   execute :rake, 'cache:clear'
    # end
  end

  after :finishing, "deploy:cleanup"
end

Note: There’s an issue with Capistrano, git and tar in FreeBSD, which you will have to work around by putting a modified GitStrategy into lib/capistrano/tasks (see the linked page for more information — the cause are syntax differences between GNU tar and bsdtar).

How to manage the application server

To initially set up Puma, use bundle exec cap production puma:config and edit the resulting puma.rb file (it’s in the shared directory of the production environment).

Now when you deploy your project, capistrano-puma automatically cooks the respective puma:start, puma:stop etc. recipes.

You may also use the puma:* recipes at any time.

It may be noteworthy that this solution does not start the application server after a reboot. To do so, I have put these lines into /usr/local/etc/rc.local (don’t forget to make it executable):

RACK_ENV=production
export RACK_ENV

for rails in myproject1 myproject2
do
        cd /srv/www/$rails/live/current
        sudo -u $rails /usr/bin/env bundle exec puma -C /srv/www/$rails/live/shared/puma.rb
done

Connecting to the Web server

The puma server is configured to listen on a UNIX socket. Make sure that the Web server (nginx) proxies this socket:

upstream myproject {
    server unix:/srv/www/myproject/live/shared/tmp/sockets/puma.sock fail_timeout=0;
}

server {
    server_name www.myproject.com;
    root /srv/www/myproject/live/current/public;

    try_files $uri @rails;
    location @rails {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;

        proxy_pass http://myproject;
    }
    location ~ ^/assets/ {
        gzip_static on;
        expires max;
        add_header Cache-Control public;
    }
    location ~ ^/(images|system)/ {
        expires 7d;
    }
}

Further reading

What we want to achieve

When an email is sent to office@my.domain, the mail should be delivered regularly, and an auto-reply email should be sent to the sender (but only one mail within, for instance, one week).

Basic method

The basic method can be read on Postfix Virtual Domain Hosting Howto: Auto-replies:

  1. When a mail is sent to office@my.domain, the virtual alias table expands office@my.domain to two destinations: office@my.domain (the “real” address) and office@autoreply.my.domain (although this subdomain doesn’t exist in real).
  2. In the transport table, the delivery service for the domain autoreply.my.domain is set to autoreply.
  3. The autoreply service delivers the email to the FreeBSD vacation utility.
  4. vacation sends a given auto-reply message, if it hasn’t be sent to the original sender in the specified interval.

Actual configuration

Add this entry to the virtual_alias_maps table (usually /usr/local/etc/postfix/virtual):

office@my.domain       office@my.domain,office@autoreply.my.domain

(Don’t forget to check virtual_alias_domains and run postmap virtual to compile the table.)

Then, set the transport_maps in main.cf and add the auto-reply domain to the transport table:

autoreply.my.domain       autoreply:

(Of course, run postmap transport again.)

Now, we have do define how the actual delivery via the “autoreply” service (the name take from the token before the colon, not the domain) shall be done (in master.cf):

play       unix  -       n       n       -       1       pipe
  flags=F user=autoreply argv=/usr/bin/vacation -a office@my.domain -R office@my.domain -f vacation-mydomain.db -m vacation-mydomain.msg autoreply

Here, the vacation tool is called. For security reasons, I have created an autoreply system user (home directory: /home/autoreply). The actual auto-reply message is stored in /home/autoreply/vacation-mydomain.msg, the list of already notified senders in vacation-mydomain.db. For details about how to call vacation, see its man page.

After running postfix reload, the auto-reply shall work. Test it and watch the log output.

SNI

If you want to use SSL/TLS with SNI (Server Name Indication) on Android, you’re basically encouraged to use HttpsURLConnection which supports SNI by default since Android 2.3.x (see Android’s HTTP Clients).

However, if you want to use other HTTP verbs than OPTIONS, HEAD, TRACE, GET, POST, PUT or DELETE (look for “HTTP Methods” in the docs), HttpsURLConnection is not an option. So you will have to stick with the HttpClient library, i.e. the DefaultHttpClient / AndroidHttpClient classes.

Apache HttpClient 4.0-alpha is shipped with Android, and it doesn’t seem that Google is willing to update the library. So, if you need a newer HttpClient version (and there are good reasons why you might need it), the HttpClient package names (Java name spaces) have to be changed. httpclientandroidlib is such a repackaging of recent HttpClient libraries to Android.

HttpClient supports SNI on Oracle’s Java 1.7 since 4.3.2, but this is not useable with Android’s Java flavour. So, HttpClient doesn’t support SNI on Android by default.

However, there are two ways to get SNI:

  1. Since Android 4.2+ (API level 17) they have officially added SNI support to a class called SSLCertificateSocketFactory.
  2. Since Android 2.3 (Gingerbread), SNI is available in the OpenSSL implementation used by Android’s Java flavour. Sockets created by SSLCertificateSocketFactory are instances of SSLSocketImpl, and this class has a method called setHostname(String) that enables SNI and sets the SNI hostname for this socket. However, this feature is not documented and can only be used by reflection. There might also be Android variants (for instance, by certain vendors) that don’t provide this method because it’s not documented.

Using these two methods, it’s possible to add SNI support for your HttpClient application, too. (See code example below).

TLS v1.1/v1.2

Android versions >= 4.1/4.2 and < 5.0 support TLS 1.1 and TLS 1.2, but these (newer and more secure) TLS versions are disabled, while only SSLv3 and TLSv1 stay enabled by default. This is fixed in Android 5.0, but for the versions between you’ll have to enable TLSv1.1 and TLSv1.2 manually:

ssl.setEnabledProtocols(ssl.getSupportedProtocols());

Working example for stock Android HttpClient

This code is for the HttpClient version which is shipped with Android (HttpClient 4.0-alpha). Look at the links below for an example using a recent HttpClient version.

// create new HTTP client
client = new ApacheHttpClient();

// use our own, SNI-capable LayeredSocketFactory for https://
SchemeRegistry schemeRegistry = client.getConnectionManager().getSchemeRegistry();
schemeRegistry.register(new Scheme("https", new TlsSniSocketFactory(), 443));

Then define your TlsSniSocketFactory:

@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public class TlsSniSocketFactory implements LayeredSocketFactory {
    private static final String TAG = "davdroid.SNISocketFactory";

    final static HostnameVerifier hostnameVerifier = new StrictHostnameVerifier();


    // Plain TCP/IP (layer below TLS)

    @Override
    public Socket connectSocket(Socket s, String host, int port, InetAddress localAddress, int localPort, HttpParams params) throws IOException {
            return null;
    }

    @Override
    public Socket createSocket() throws IOException {
            return null;
    }

    @Override
    public boolean isSecure(Socket s) throws IllegalArgumentException {
            if (s instanceof SSLSocket)
                    return ((SSLSocket)s).isConnected();
            return false;
    }


    // TLS layer

    @Override
    public Socket createSocket(Socket plainSocket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
            if (autoClose) {
                    // we don't need the plainSocket
                    plainSocket.close();
            }

            // create and connect SSL socket, but don't do hostname/certificate verification yet
            SSLCertificateSocketFactory sslSocketFactory = (SSLCertificateSocketFactory) SSLCertificateSocketFactory.getDefault(0);
            SSLSocket ssl = (SSLSocket)sslSocketFactory.createSocket(InetAddress.getByName(host), port);

            // enable TLSv1.1/1.2 if available
            // (see https://github.com/rfc2822/davdroid/issues/229)
            ssl.setEnabledProtocols(ssl.getSupportedProtocols());

            // set up SNI before the handshake
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                    Log.i(TAG, "Setting SNI hostname");
                    sslSocketFactory.setHostname(ssl, host);
            } else {
                    Log.d(TAG, "No documented SNI support on Android <4.2, trying with reflection");
                    try {
                         java.lang.reflect.Method setHostnameMethod = ssl.getClass().getMethod("setHostname", String.class);
                         setHostnameMethod.invoke(ssl, host.getHostName());
                    } catch (Exception e) {
                            Log.w(TAG, "SNI not useable", e);
                    }
            }

            // verify hostname and certificate
            SSLSession session = ssl.getSession();
            if (!hostnameVerifier.verify(host, session))
                    throw new SSLPeerUnverifiedException("Cannot verify hostname: " + host);

            Log.i(TAG, "Established " + session.getProtocol() + " connection with " + session.getPeerHost() +
                            " using " + session.getCipherSuite());

            return ssl;
    }
}

Copyright notice: To the extent possible under law, I have dedicated all copyright and related and neighboring rights to the software (source code) in this article to the public domain worldwide. This software is distributed without any warranty.

If you want to see real code working together with a recent HttpClient version, see here: DAVdroid DavHttpClient and TlsSniSocketFactory.

Have fun!

As a former Android evangelist, I have reconsidered my views and have come to the conclusion that Android is not an “open” but a proprietary platform. Google themselves say that Android is not the goal, but a “vehicle” to ensure all people (including the ones using other platforms like Google’s own Chrome platform, but also iOS and Windows) use and are fully dependent on all the other Google services.

To make it short: Android is as “open” as Windows 2000 when the source code has been leaked.

What makes the difference between an “open” platform and a proprietary one? Let’s have a look at some properties of the Android platform:

  • You (as the customer, not as a device manufacturer) can’t modify it for yourself (because your device manufacturer puts it onto your device and you are not allowed to be root on your own device; if you flash it, you lose your warranty)
  • You can’t modify it for others (for the same reason: if you fork and release a modified version yourself, nobody will use it because as a user, you can’t decide which system you use – you get a version from your device manufacturer or service provider, and those cooperate with Google and won’t do anything that Google doesn’t want). You may of course do some free work for Google and contribute patches, but you may only do the dirty work. Decisions are made by Google only, APIs are designed by Google only.
  • Strategy decisions are made by Google for the sole purpose of increasing market share and sales (that’s what companies do). There’s no claim to be open or fair, there are no rules.
  • You don’t have the possibility to get involved in any way. Google has absolute power over the whole project, there is no democratic cooperation with developers – It’s sink or swim. In contrast, other Linux flavours are developed in a much more open way.
  • From the beginning, there have been questionable decisions regarding open formats, tools etc. How many Linux systems without gzip, bzip2 and Ext support do you know? Why doesn’t MTP work with connected Linux PCs (I tried several MTP clients) but “requires Windows XP SP3+” (that’s what a Samsung Note 10.1 told me when I tried to connect it with a Linux PC using MTP; of course file transfers > 1 GB always fail)?
  • Android is only a “vehicle” on the way to make all people depend on Google services. It forces you to use proprietary Google services (Gmail instead of email [WHY do I need a GMAIL account to use Android? A Google account with every other email address would be enough, but no, it has to be GMail!], Google Calendar instead of CalDAV, Hangouts instead of XMPP, Google+ instead of RSS/Atom [even if Reader won’t rise from the dead, it’s obvious that Google wants all content providers to “share on Google+” instead of providing an RSS feed])
  • It forces you to do things you don’t want to do (I don’t want to have a Google+ profile, never did, and now I can’t rate apps any more because a Google+ profile is required for that). Also, nearly every click on any Google site encourages me to finally create my Google+ profile and drown into debility, or to enter my real name or mobile phone number etc.
  • Oh, apps – the Play market is not “open” because there’s an entry fee and non-conforming apps like ad-blockers are being removed from the market.
  • They call it “open” and emphasise that it’s based on Linux just to make people think they are the “good ones”. (Also think about the Summer of Code – how many money does Google spend and what’s the purpose of all this?)
  • Of course, Google doesn’t give a sh*t about data protection and ignores laws (at least in the EU), but that’s another story.

Summary: They have chosen Linux to get a good base system and the “open-source” or “free software bonus” in the geek scene, but Android is a fully proprietary system whose only purpose is to increase the market share of proprietary Google services. There’s nothing open about it.

If you’re concerned about open platforms, you may have to look for alternatives.

Drawn with GfxTablet :)