Antoine Amarilli's blog

Android from the command-line

— updated

Something I hate about Android is that there are almost no bridges between the usual *nix command-line world and the brave new world of Java. If things had been done properly, for instance, you would be able to ssh to your phone and run a trivial bash one-liner to save the current GPS coordinates to the SD card every 30 seconds, instead of having to write a verbose Java application to do that (or installing a third-party app which will come with all sorts of bells and whistles). Also, if things had been done properly, there wouldn't be obscure terminal glitches and bizarre and gratuitous deviations from the FHS...

Now, my Android phone fell, the screen broke, and I am left with a perfectly functional ARM-based computer with a lot of usable hardware, such as:

  • Wifi with master mode support
  • Speakers and headphone plug
  • Microphone and camera
  • GPS
  • Vibrator and notification LED
  • GSM
  • Bluetooth

I have root access to the device, I can get a shell through adb, there are a lot of cool things that I could do with this if only using Android from the command-line wasn't that much of a pain. So here are a few notes I took as I explored all of this, in case someone finds them useful. The phone is a HTC Desire, the ROM is Cyanogen 7.1.0 if I remember correctly (not that it matters), I am root, and NAND protection is disabled (aka. S-OFF).

Connecting to WiFi (managed mode)

It is not that hard to connect to WiFi (using wpa_supplicant and dhcpcd) except for a few quirks. I assume that you are familiar with wpa_supplicant configuration files and with wpa_cli's rudimentary interface (just a tip if you didn't know: you just need to type non-ambiguous prefixes for commands).

# this will fail unless you are S-OFF
mount -o remount,rw /
mount -o remount,rw /system
# took some time to find out
insmod /system/lib/modules/bcm4329.ko 
# yes, the wifi interface is called "eth0"...
mkdir /eth0
cd /
# edit /etc/wifi/wpa_supplicant.conf to define your APs
wpa_supplicant -B -Dwext -ieth0 -c/etc/wifi/wpa_supplicant.conf
# use wpa_cli to select the AP to associate with
wpa_cli -p /eth0/
pkill dhcpcd
# remove dhcp leases
rm -Rf /data/misc/dhcp/*
dhcpcd eth0
# beware: DNS is slow and not very reliable, if pinging a domain doesn't work
# try pinging an IP before declaring failure

Setting the time

The time is reset each time you remove the battery, and of course you must set it from the command line. To do so, if you have an Internet connection, an easy way is: ntpd -p 64.90.182.55. (Alternatively, substitute the IP of your favorite public NTP server.) Don't forget to do this, or you can get weird errors (for instance, openvpn will complain about certificates not being valid yet).

Getting Debian

Since Android does not come with any sort of package manager, the standard way to install stuff on Android is by chrooting in a Debian install. This process is pretty well documented here and here so I won't explain it all over again. Notice however that the first guide forgets to say that you should export TERM=xterm and export PATH="/bin:/usr/bin:/usr/sbin:$PATH just after chrooting. If you follow the first guide, you will get a VNC server, so you can VNC to your phone and interact with graphical programs. (Graphical programs from the chrooted Debian, of course, not from Android.) Not extremely useful, but pretty neat.

Creating a WiFi access point (master mode)

This is much more difficult. First, you must load the WiFi module with an alternative firmware. (Obviously, rmmod it first if it is already loaded. Or, to be sure, reboot the phone...)

insmod /system/lib/modules/bcm4329.ko firmware_path=/system/vendor/firmware/fw_bcm4329_apsta.bin

Now, using iwconfig directly doesn't seem to work. The only reproducible way I found to reliably create an AP is to use the mysterious binary program res/raw/ultra_bcm_config from android-wifi-tether. Check out the code, send this file to the phone (using adb push or anything else) and run (where AP is the name of the access point you would like to create and CHAN is the channel):

ultra_bcm_config eth0 softap_htc AP none "" CHAN

I wonder what this program does. Next, perform:

iwconfig eth0 essid AP
ifconfig eth0 address 192.168.2.1

Now, you need to serve DHCP leases. Android uses dnsmasq to do this.

mkdir -p /var/run
cat > /data/dnsmasq.conf <<EOF
dhcp-authoritative 
interface=eth0
dhcp-range=192.168.2.100,192.168.2.105,12h
user=root
no-negcache
EOF
dnsmasq -C /data/dnsmasq.conf

You should now be able to connect to the phone and get a DHCP lease. Hurray!

Changing your MAC

The usual command ip link set eth0 address 00:11:22:33:44:55 will sometimes work. If it doesn't, reboot and try again.

Creating a captive portal

I didn't even try to get a data connection over GSM operational from the command-line (I would probably need the screen to enter the SIM PIN anyway) and I didn't try yet to "reverse-tether" and create an AP to share an ethernet connection (going through a laptop, ie. the laptop has ethernet connection, the phone is plugged to the laptop and shares the connection over WiFi). Hence, the phone cannot have Internet access when it creates an access point. Yet, there are a lot of neat things to do with a battery-powered WiFi AP that you can carry in your pocket. A WiDrop? Access to a static Wikipedia mirror?

Yet, to do that, you need to present these services to the user. As far as I know, there is no standard describing how this should be done, and the best you can do is intercept all HTTP traffic and hope that the user is running a web browser... Here is a simple solution which does not require you to install real fat captive portal software. Warning, just in case you're skim-reading through this, the setup described here only tries to intercept HTTP connections, not every kind of traffic--don't do this if the phone has an Internet connection somehow and you want to be sure the clients are blocked for real.

First, we need to ensure that clients can get DNS service. Since usual DNS servers are not reachable (because the phone has no Internet connection), we need to (a.) run a DNS server on the device and (b.) intercept all DNS requests to third party hosts and answer them ourselves. Pretty easy to do, since dnsmasq can serve as a lightweight DNS server:

cat >> /data/dnsmasq.conf <<EOF
address=/#/192.168.2.1 
EOF
iptables -t nat -A PREROUTING -p udp --dport 53 -j REDIRECT

Second, we need to redirect all HTTP connections towards ourselves, just in case someone tries an HTTP connection without using DNS first (you never know, especially if the client has a DNS cache or something like that).

iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.2.1

Third, we need to run an HTTP server on the device. The easiest way to do that is to run it within the Debian chroot. It just works.

Fourth, we need the HTTP server to answer even if a weird path is requested. The quick and dirty way to do this is to set up the 404 page as a redirection to the index page. For instance, with lighttpd:

server.error-handler-404 = "/index.html"

Of course, this messes up the HTTP status code, though. Finding a better solution is left as an exercise. (For my purposes, I don't really care.)

Knowing the battery status

Don't even think about using acpi! I hunted for relevant stuff in /sys but couldn't find anything, so the only approach I found is:

dmesg | grep batt: | tail -1

Lars Rasmusson pointed out that relevant files are /sys/devices/platform/msm-battery/power_supply/battery/voltage_*. It might depend on your phone model, but it is probably a good idea to run find power_supply /sys.

Activating the vibrator

The vibrator is a useful way to get notified about what happens.

# the bigger the value, the longer the vibration will last
echo 100 > /sys/devices/virtual/timed_output/vibrator/enable

If you want to trigger the vibrator from a program which isn't run as root (e.g. the HTTP server), don't forget to change the permissions of the file.

Activating the flashlight

# values can be between 1 and 255, though the led seems to power down by itself
# for values over 128, maybe because the hardware cannot sustain them for long
echo 1 /sys/devices/platform/flashlight.0/leds/flashlight/brightness

Playing music

An extremely pleasant surprise: there is a command to play a sound file!

stagefright -a -o file.mp3

An extremely unpleasant surprise: the command isn't documented and is extremely limited. Probably a debugging tool that someone forgot to remove.

Webcam acquisition

Don't get your hopes up, I didn't manage to get this to work. If you're only interested in stuff that works, you can stop reading this post here.

Now, here are some notes about what I tried. There is no /dev/video0 device and there doesn't seem to be any comparable device anywhere, so my guess is there is no V4L support. I ran mplayer and vlc from the Debian chroot in the hope that things could automagically work, but to no avail. Apparently, the Android piece of software which handles the webcam is called Stagefright, the documentation of which is a blogpost by a Google intern and the source code itself (think there must be better docs? think again). The job is split between Java (which provide the bindings that regular app developers use) and C++ (the native code you're not supposed to be writing) which performs the actual work including stuff like hardware acceleration of some codecs. Apparently, someone did something clever: an stagefright interface for libav. Sadly, the only documentation available, this time, is a Youtube video. I managed to compile it after days of stupid mistakes (if you want to try it yourself, make sure you check out the right branch, use tools/build_libstagefright with the NDK variable set to the path to the Android NDK (which contains the cross-compiler), and use make V=1 to troubleshoot issues). Sadly, it either hangs or fails with unhelpful error messages at runtime. Maybe it's because the guy doesn't have the same phone model. The next way would be to use gdb or something like that, but I'm a bit discouraged... please tell me if you managed to make this work.

comments welcome at a3nm<REMOVETHIS>@a3nm.net