Android from the command-line
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.
As Daniel Wallenborn pointed out,
recent open source applications such as Lil' Debi or Debian Kit can now
be used to make this process even simpler.
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. Ogurets pointed out that webcam aquisition is possible using ad-hoc code on some chipsets to invoke ioctl, for instance with the vc088x SoC. Ogurets writes: This stuff is very hardware-dependent, you could quickly check your hardware by listing /dev: if you have "cif", "video", "venc", "v8-tv" devs, then you have a VC088x SoC and the code will work for you. Otherwise, you could download kernel source code for your device (the manufacturer has to publish it due to GPL) and find proper ioctls for your camera, then write a similar program for yourself. I have not tested this code on the HTC Desire, however. For completeness I reproduce below my original steps to try to get the webcam to work, even though they seem much less promising.
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.