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.