Content-Type: multipart/related; start=<op.mhtml.1371249003175.c4c10165012abc04@::1%1>; boundary=----------wZoqbsMzLzmJPSOFZTLHEI
Content-Location: http://a3nm.net/blog/android_cli.html
Subject: =?utf-8?Q?Android=20from=20the=20command-line=20-=20Antoine=20Amarilli's=20blog?=
MIME-Version: 1.0

------------wZoqbsMzLzmJPSOFZTLHEI
Content-Disposition: inline; filename=android_cli.html
Content-Type: text/html; charset=utf-8; name=android_cli.html
Content-ID: <op.mhtml.1371249003175.c4c10165012abc04@::1%1>
Content-Location: http://a3nm.net/blog/android_cli.html

<!DOCTYPE html>
<html dir="ltr" xml:lang="en-US" lang="en-US" xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>
      Android from the command-line - 
      Antoine Amarilli's blog
    </title>
    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
    <meta name="author" content="Antoine Amarilli" />
    <meta name="description" content="Antoine Amarilli's blog" />
    <link rel="alternate" type="application/rss+xml" href="http://ulminfo.fr/~a3_nm/blog/feed.xml" title="RSS feed of last 5 articles" />
    <link rel="stylesheet" href="fugitive.css" type="text/css" media="screen" />
    <link rel="stylesheet" href="print.css" type="text/css" media="print" />
    <link rel="contents" href="archives.html" />
    <link rel="canonical" href="http://a3nm.net/blog/android_cli.html" />
    <link rel="prev" href="crowdsourcing_ethics.html" />
    <link rel="next" href="identity.html" />
  </head>
  <body>
    <div id="container">
      <nav id="topnav">
        <ul class="nav">
          <li>
            <a href="crowdsourcing_ethics.html">
              <span>&lt; prev</span>
            </a>
          </li>
          <li class="fixed">
            <a href="identity.html">
              <span>next &gt;</span>
            </a>
          </li>
        </ul>
        <ul>
          <li><a href="index.html">latest</a></li>
          <li><a href="archives.html">all</a></li>
          <li><a href="feed.xml">rss</a></li>
          <li><a href="http://a3nm.net">website</a></li>
        </ul>
      </nav>
      <header>
      <h1><a href="index.html">Antoine Amarilli's blog</a></h1>
      </header>
      <article>
        <header>
        <h1><a href="android_cli.html">
            Android from the command-line
        </a></h1>
        </header>
        <footer>
          <time datetime="2012-04-09T22:26:20+02:00">
            2012-04-09 22:26:20
          </time>
          <small>&mdash; updated
            <time datetime="2012-10-27T18:28:42+02:00">
              2012-10-27 18:28:42
            </time>
          </small>
        </footer>
        <div>
<p>Something I <em>hate</em> 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...</p>
<p>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:</p>
<ul>
<li>Wifi with master mode support</li>
<li>Speakers and headphone plug</li>
<li>Microphone and camera</li>
<li>GPS</li>
<li>Vibrator and notification LED</li>
<li>GSM</li>
<li>Bluetooth</li>
</ul>
<p>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 <a
href="https://en.wikipedia.org/wiki/HTC_Desire">HTC Desire</a>, the ROM is <a
href="http://www.cyanogenmod.com/">Cyanogen</a> 7.1.0 if I remember correctly
(not that it matters), I am root, and NAND protection is disabled (aka.
S-OFF).</p>
<h2>Connecting to WiFi (managed mode)</h2>
<p>It is not that hard to connect to WiFi (using <code>wpa_supplicant</code> and
<code>dhcpcd</code>) 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).</p>
<pre>
# 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
</pre>
<h2>Setting the time</h2>
<p>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: <code>ntpd -p 64.90.182.55</code>. (Alternatively, substitute the
IP of your favorite public NTP server.) Don't forget to do this, or you can get
weird errors (for instance, <code>openvpn</code> will complain about
certificates not being valid yet).</p>
<h2>Getting Debian</h2>
<p>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 <a
href="http://www.mayrhofer.eu.org/debian-on-android">here</a> and <a
href="http://www.saurik.com/id/10">here</a> so I won't explain it all over
again. Notice however that the first guide forgets to say that you should
<code>export TERM=xterm</code> and <code>export
PATH="/bin:/usr/bin:/usr/sbin:$PATH</code> 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.</p>
<h2>Creating a WiFi access point (master mode)</h2>
<p>This is much more difficult. First, you must load the WiFi module with an
alternative firmware. (Obviously, <code>rmmod</code> it first if it is already
loaded. Or, to be sure, reboot the phone...)</p>
<pre>
insmod /system/lib/modules/bcm4329.ko firmware_path=/system/vendor/firmware/fw_bcm4329_apsta.bin
</pre>
<p>Now, using <code>iwconfig</code> directly doesn't seem to work. The only
reproducible way I found to reliably create an AP is to use the mysterious
binary program <code>res/raw/ultra_bcm_config</code> from <a
href="https://code.google.com/p/android-wifi-tether/">android-wifi-tether</a>.
Check out the code, send this file to the phone (using <code>adb push</code> or
anything else) and run (where AP is the name of the access point you would like
to create and CHAN is the channel):</p>
<pre>
ultra_bcm_config eth0 softap_htc AP none "" CHAN
</pre>
<p>I wonder what this program does. Next, perform:</p>
<pre>
iwconfig eth0 essid AP
ifconfig eth0 address 192.168.2.1
</pre>
<p>Now, you need to serve DHCP leases. Android uses <code>dnsmasq</code> to do
this.</p>
<pre>
mkdir -p /var/run
cat > /data/dnsmasq.conf &lt;&lt;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
</pre>
<p>You should now be able to connect to the phone and get a DHCP lease.
Hurray!</p>
<h2>Changing your MAC</h2>
<p>The usual command <code>ip link set eth0 address 00:11:22:33:44:55</code>
will <em>sometimes</em> work. If it doesn't, reboot and try again.</p>
<h2>Creating a captive portal</h2>
<p>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 <a href="http://widrop.bzhack.org/">WiDrop</a>? Access to a
static Wikipedia mirror?</p>
<p>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.</p>
<p>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
<code>dnsmasq</code> can serve as a lightweight DNS server:</p>
<pre>
cat >> /data/dnsmasq.conf &lt;&lt;EOF
address=/#/192.168.2.1 
EOF
iptables -t nat -A PREROUTING -p udp --dport 53 -j REDIRECT
</pre>
<p>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).</p>
<pre>
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.2.1
</pre>
<p>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.</p>
<p>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:</p>
<pre>
server.error-handler-404 = "/index.html"
</pre>
<p>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.)</p>
<h2>Knowing the battery status</h2>
<p>Don't even <em>think</em> about using <code>acpi</code>! I hunted for
relevant stuff in <code>/sys</code> but couldn't find anything, so the only
approach I found is:</p>
<pre>
dmesg | grep batt: | tail -1
</pre>
<p><span class="update">Lars Rasmusson pointed out that relevant files are
<code>/sys/devices/platform/msm-battery/power_supply/battery/voltage_*</code>.
It might depend on your phone model, but it is probably a good idea to run
<code>find power_supply /sys</code>.</span></p>
<h2>Activating the vibrator</h2>
<p>The vibrator is a useful way to get notified about what happens.</p>
<pre>
# the bigger the value, the longer the vibration will last
echo 100 > /sys/devices/virtual/timed_output/vibrator/enable
</pre>
<p>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.</p>
<h2>Activating the flashlight</h2>
<pre>
# 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
</pre>
<h2>Playing music</h2>
<p>An extremely pleasant surprise: there is a command to play a sound file!</p>
<pre>
stagefright -a -o file.mp3
</pre>
<p>An extremely unpleasant surprise: the command isn't documented and is
extremely limited. Probably a debugging tool that someone forgot to remove.</p>
<h2>Webcam acquisition</h2>
<p>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.</p>
<p>Now, here are some notes about what I tried. There is no
<code>/dev/video0</code> device and there doesn't seem to be any comparable
device anywhere, so my guess is there is no <a
href="http://en.wikipedia.org/wiki/Video4Linux">V4L</a> 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
href="http://boundarydevices.com/blogs/camera-subsystem-overview-for-i-mx-gingerbread">a
blogpost by a Google intern</a> and the source code itself (think there must be
better docs? <a
href="http://stackoverflow.com/questions/5883086/stagefright-architecture">think
again</a>). The job is split between Java (which provide the bindings that
regular app developers use) and C++ (the <em>native</em> 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:
<a href="https://github.com/dmonakhov/libav/tree/stagefright-devel">an
stagefright interface for libav</a>. Sadly, the only documentation available,
this time, is <a href="http://youtu.be/-d5lzKk1hPU">a Youtube video</a>. I
managed to compile it after days of stupid mistakes (if you want to try it
yourself, <em>make sure you check out the right branch</em>, use
<code>tools/build_libstagefright</code> with the NDK variable set to the path to
the Android NDK (which contains the cross-compiler), and use <code>make
V=1</code> 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.</p>
        </div>
      </article>
      <div id="comments">
        comments welcome at a3nm<span
          style="display:none;">&lt;REMOVETHIS&gt;</span>@a3nm.net
      </div>
      <nav>
        <p class="fixed" style="float:left;">
          <a href="crowdsourcing_ethics.html">
            <span>&lt; prev</span>
          </a>
        </p>
        <p class="fixed" style="float:right;">
          <a href="identity.html">
            <span>next &gt;</span>
          </a>
        </p>
        <p style="text-align: center;">&mdash; <a
          href="archives.html">all posts</a> &mdash;</p>
      </nav>
      <footer>
        <p>
          author <a href="http://a3nm.net/">Antoine Amarilli</a>
          &mdash;
          license <a
              href="http://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>
          &mdash;
          engine <a href="http://gitorious.org/fugitive">fugitive</a>
        </p>
        <p>
          updated <time>2013-05-24T18:52:30+02:00</time>
        </p>
      </footer>
    </div>
  </body>
</html>

------------wZoqbsMzLzmJPSOFZTLHEI
Content-Disposition: inline; filename=fugitive.css
Content-Type: text/css; charset=UTF-8; name=fugitive.css
Content-Location: http://a3nm.net/blog/fugitive.css

@charset "UTF-8"; 

* { margin: 0; padding: 0; }
html, body {
  background-color: #fff;
  margin: 0;
  padding: 0;
  font-family: sans;
  /*font-family: DejaVu Sans Mono, Liberation Mono, monospace;*/
  font-size: 1em;
  /*text-align: center;*/
  color: #333;
}
#container {
  padding: 0 0.4em;
  margin: 0 auto;
  min-width: 28em;
  max-width: 36em;
}
#container header a, #container h1 a, #container h2 a,
#container h3 a, #container h4 a {
  color: #000;
  text-decoration: none;
}
pre a, pre a:link {
  color: white;
}
#container a:hover, #container a:active { color: #251; }
pre > a:hover, pre > a:active { color: #8a7 !important;}
pre > a:visited { color: #a5c !important; }
#container footer p a:hover, #container footer p a:active { color: #251; }
#container > footer {
  text-align: center;
}
nav, #comments {
  display: block;
  margin: 0;
  padding: 0.3em 1em;
  font-family: monospace;
}
#comments{
  border-top: 1px solid #ccc;
  font-size: 80%;
  text-align: center;
}
nav {
  border-width: 1px;
  border-color: #ccc;
  border-style: solid none;
  background: #f2f2f2;
  text-align: left;
  color: #aaa;
  font-family: monospace;
}
time {
  font-family: monospace;
}
#topnav {
  border-style: none solid solid solid;
  font-size: 1.3em;
}
nav ul { padding: 0; margin: 0; }
nav ul.nav {
  float: right;
  width: 40%;
  text-align: right;
}
nav ul li { 
  display: inline;
}
nav .fixed span { visibility: hidden; }
nav .fixed a span { visibility: visible; }
nav ul li:before { content: " — "; }
nav ul li:first-child:before { content: ""; }
nav ul li a {
  color: black;
  text-decoration: none;
}

#container nav ul li a:hover, #container nav ul li a:active { color: #251; }
header {
  display: block;
  margin: 1em 0;
  padding: 1em;
  border: 4px double #ccc;
}
header h1 {
  margin: 0 0 0 0;
  padding: 0;
  font-size: 2.5em;
  font-weight: normal;
  text-align: center;
}
#container > header {
  font-family: monospace;
}
header q {
  font-size: 1.2em;
  font-style: italic;
}
article {
  display: block;
  text-align: justify;
  margin-bottom: 3em;
  counter-reset: code figure example section subsection;
}
article header {
  display: block;
  margin: 2em 0 0 0; 
  padding: 0;
  border-style: none;
  border-bottom: 1px dashed #ccc;
}
article header h1 {
  margin: 0;
  padding: 0 0 0.1em 0;
  font-size: 1.35em;
  font-weight: bold;
  text-align: left;
}
article footer {
  display: block;
  margin: 0.3em 0 0 0;
  padding: 0;
  border-style: none;
  color: #888;
  font-size: 1em;
  text-align: right;
}
article > div {
  margin: 0;
  padding: 0;
  line-height: 1.5em;
}
article > div a {
  color: #048;
  text-decoration: underline;
}
article > div a:hover, article > div a:active { text-decoration: underline; }
article > div a:visited { color: #608; }
article > div h2 {
  margin: 1em 0.5em 0.5em 0.5em;
  font-size: 1.2em;
  font-weight: normal;
  counter-increment: section;
  counter-reset: subsection;
}
article > div h2:before {
  display: inline;
  content: counter(section) ". ";
}
article > div h3 {
  margin: 1em 0 0.5em 0;
  font-size: 1.07em;
  font-weight: bold;
  counter-increment: subsection;
}
article > div h3:before {
  display: inline;
  content: counter(section) "." counter(subsection) ". ";
}
article > div h4 {
  margin: 1em 0 0.5em 0;
  font-size: 1.03em;
  font-weight: bold;
}
article > div > p:first-child:first-letter {
  font-size: 1.5em;
  font-weight: bold;
}
article > div > p.first:first-letter {
  font-size: 1.5em;
  font-weight: bold;
}
article > div > p.notfirst:first-letter {
  font-size: 1em;
}
article > div p {
  margin: 0.5em 0.5em;
  text-indent: 2em;
}
article > div p.important:before {
  content: "important: ";
  color: #f00;
  font-weight: bold;
  font-variant: small-caps;
}
article > div p.warning:before {
  content: "warning: ";
  color: #fa0;
  font-weight: bold;
  font-variant: small-caps;
}
article > div p.info:before {
  content: "info: ";
  color: #080;
  font-weight: bold;
  font-variant: small-caps;
}
article > div p.note:before {
  content: "note: ";
  font-weight: bold;
  font-variant: small-caps;
}
article > div p.example { counter-increment: example; }
article > div p.example:before {
  content: "example " counter(example) ":";
  font-variant: small-caps;
}
article > div aside {
  display: block;
  padding: 0 0.5em;
  margin: 0.5em 0 0.5em 0.5em;
  width: 36%;
  float: right;
  border: 1px solid #ccc;
  text-align: justify;
  background-color: #f2f2f2;
}
article > div aside p {
  font-size: 0.9em; 
  margin: 0.5em 0;
}
article > div ul, article > div ol {
  /*margin: 1em 0;*/
  padding: 0 0.5em 0 1.5em;
}
article > div ul > li {
  padding: 0 0 0 0.5em;
  list-style-position: outside;
  list-style-type: circle;
}
article > div li {
  margin: 0 0 0.2em 1em;
}

article > div dl {
  margin: 1em 0;
  padding: 0;
}
article > div dl dt {
  margin: 0;
  padding: 0;
  font-weight: bold;
}
article > div dl dd {
  margin: 0 0 0.3em 0;
  padding: 0 0 0 2em;
}
article > div blockquote {
  margin: 1em 0;
  padding: 0.5em;
  padding-left: 2em;
  background: #f2f2f2;
  border-left: 0.1em solid #ccc;
}
article > div blockquote p { margin: 0.5em 0; }
article > div code {
  color: #eee;
  background-color: #666;
  padding: 0 0.2em;
}
article > div pre {
  margin: 1em 0;
  padding: 0.5em;
  overflow: auto;
  color: #ccc;
  background-color: #333;
  max-height: 420px;
}
article > div pre .comment { color: #888; }
article > div pre .comment-delimiter { color: #888; }
article > div pre .constant { color: #d0d; }
article > div pre .function-name { color: #27d; }
article > div pre .keyword { color: #dd0; }
article > div pre .preprocessor { color: #9c3; }
article > div pre .string { color: #3af; }
article > div pre .type { color: #fa0; }
article > div pre .variable-name { color: #0b0; }
article > div figure {
  display: block;
  margin: 1em 0;
  padding: 0;
  text-align: center;
}
article > div figure figcaption {
  display: block;
  counter-increment: figure;
}
article > div figure.example figcation { counter-increment: example; }
article > div figure figcaption:before {
  display: inline;
  content: "figure " counter(figure) ":";
  font-variant: small-caps;
}
article > div figure.example figcaption:before {
  display: inline;
  content: "example " counter(example) ":";
  font-variant: small-caps;
}
article > div .left {
  float: left;
  margin: 0 1em 0.5em 0;
}
article > div .right {
  float: right;
  margin: 0 0 0.5em 1em;
}
article > div .center { text-align: center; }
article > div hr {
  margin: 1.5em auto;
  padding: 0;
  height: 1px;
  width: 80%;
  border: none;
  background-color: #aaa;
}
footer {
  display: block;
  margin: 1em 0 0 0;
  padding: 1em;
  border-top: 1px dotted #aaa;
  color: #888;
  font-size: 0.9em;
  clear: both;
  font-family: monospace;
}
footer p {
  margin: 0;
  padding: 0;
  line-height: 1.5em;
}
footer p a {
  color: #666;
  text-decoration: none;
}
.xsampa, .monospace {
  font-family: DejaVu Sans Mono, Liberation Mono, monospace;
}
.foreign {
  font-style: italic;
}
.update:before { content: "[Update: "; font-weight: bold;}
.update:after { content: "]"; font-weight: bold;}



------------wZoqbsMzLzmJPSOFZTLHEI
Content-Disposition: inline; filename=print.css
Content-Type: text/css; name=print.css
Content-Location: http://a3nm.net/blog/print.css

#container {
  width: auto;
  padding: 1em;
  margin: 0;
}
nav, header, footer {
  display: none;
}
article header, article footer {
  display: block;
}

------------wZoqbsMzLzmJPSOFZTLHEI--
