diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..de77168d0816f3697c753b55f676d79a54344317
--- /dev/null
+++ b/android/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="net.i2p.router"
+      android:versionCode="1"
+      android:versionName="1.0.0">
+    <uses-permission android:name="android.permission.INTERNET" />
+    <!-- 3 = 1.5, 2 = 1.1, 1 = 1.0; would probably work with 1 but don't have a 1.0 SDK to test against -->
+    <uses-sdk android:minSdkVersion="2" />
+    <application android:label="@string/app_name">
+        <activity android:name=".I2PAndroid"
+                  android:label="@string/app_name"
+                  android:launchMode="singleTask" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest> 
diff --git a/android/README.txt b/android/README.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e8886a57988c11c4ef34f68456640a73d79a69dc
--- /dev/null
+++ b/android/README.txt
@@ -0,0 +1,25 @@
+These instructions are for the 1.5 SDK.
+The build file is not compatible with the 1.1 SDK any more.
+
+#Unzip the android SDK in ../../
+#So then the android tools will be in ../../android-sdk-linux_x86-1.5_r2/tools/
+
+#then build the android apk file:
+ant debug
+
+# Create the android 1.5 virtual device
+# (don't make a custom hardware profile)
+../../android-sdk-linux_x86-1.5_r2/tools/android create avd --name i2p --target 2
+
+#then run the emulator:
+../../android-sdk-linux_x86-1.5_r2/tools/emulator -avd i2p &
+
+#then wait a couple minutes until the emulator is up
+#then install the I2P app
+ant install
+
+#then run the debugger
+../../android-sdk-linux_x86-1.5_r2/tools/ddms &
+
+#to rebuild and reinstall to emulator:
+ant reinstall
diff --git a/android/build.xml b/android/build.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2b8664dcaae331b04e90221f09c634746b851c70
--- /dev/null
+++ b/android/build.xml
@@ -0,0 +1,352 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name=".I2PAndroid" default="help">
+
+    <!-- The local.properties file is created and updated by the 'android' tool.
+         It contain the path to the SDK. It should *NOT* be checked in in Version
+         Control Systems. -->
+    <property file="local.properties"/>
+
+    <!-- The build.properties file can be created by you and is never touched
+         by the 'android' tool. This is the place to change some of the default property values
+         used by the Ant rules.
+         Here are some properties you may want to change/update:
+
+         application-package
+             the name of your application package as defined in the manifest. Used by the
+             'uninstall' rule.
+         source-folder
+             the name of the source folder. Default is 'src'.
+         out-folder
+             the name of the output folder. Default is 'bin'.
+
+         Properties related to the SDK location or the project target should be updated
+          using the 'android' tool with the 'update' action.
+
+         This file is an integral part of the build system for your application and
+         should be checked in in Version Control Systems.
+
+         -->
+    <property file="build.properties"/>
+
+    <!-- The default.properties file is created and updated by the 'android' tool, as well
+         as ADT. 
+         This file is an integral part of the build system for your application and
+         should be checked in in Version Control Systems. -->
+    <property file="default.properties"/>
+
+    <!-- Custom Android task to deal with the project target, and import the proper rules.
+         This requires ant 1.6.0 or above. -->
+    <path id="android.antlibs">
+        <pathelement path="${sdk-location}/tools/lib/anttasks.jar" />
+        <pathelement path="${sdk-location}/tools/lib/sdklib.jar" />
+        <pathelement path="${sdk-location}/tools/lib/androidprefs.jar" />
+        <pathelement path="${sdk-location}/tools/lib/apkbuilder.jar" />
+        <pathelement path="${sdk-location}/tools/lib/jarutils.jar" />
+    </path>
+
+    <taskdef name="setup"
+        classname="com.android.ant.SetupTask"
+        classpathref="android.antlibs"/>
+
+    <!-- Execute the Android Setup task that will setup some properties specific to the target,
+         and import the rules files.
+         To customize the rules, copy/paste them below the task, and disable import by setting
+         the import attribute to false:
+            <setup import="false" />
+         
+         This will ensure that the properties are setup correctly but that your customized
+         targets are used.
+    -->
+    <setup import="false" />
+
+    <!--
+         ================================================================================
+         New I2P rules
+         ================================================================================
+    -->
+
+    <target name="buildrouter" depends="dirs" >
+        <!-- build router and core -->
+        <ant dir=".." target="buildrouter" />
+
+        <!-- router -->
+        <copy file="../build/router.jar" todir="${external-libs-folder}" />
+
+        <!-- core -->
+        <mkdir dir="tmp" />
+        <unjar src="../build/i2p.jar" dest="tmp/" />
+        <delete file="tmp/net/i2p/util/LogWriter.class" />
+        <!-- org.bouncycastle.crypto already in android
+             but we need a little trickery because our HMac is incompatible...
+             and the libs aren't in the SDK to compile against??? -->
+        <jar destfile="${external-libs-folder}/crypto.jar" >
+            <fileset dir="tmp/" >
+                <include name="org/bouncycastle/crypto/Digest.class" />
+                <include name="org/bouncycastle/crypto/Mac.class" />
+                <include name="org/bouncycastle/crypto/digests/GeneralDigest.class" />
+                <include name="org/bouncycastle/crypto/digests/MD5Digest.class" />
+            </fileset>
+        </jar>
+        <delete>
+            <fileset dir="tmp/" >
+                <include name="org/bouncycastle/crypto/Digest.class" />
+                <include name="org/bouncycastle/crypto/Mac.class" />
+                <include name="org/bouncycastle/crypto/digests/GeneralDigest.class" />
+                <include name="org/bouncycastle/crypto/digests/MD5Digest.class" />
+            </fileset>
+        </delete>
+<!--
+        <delete dir="tmp/org/bouncycastle/" />
+        <delete file="tmp/net/i2p/crypto/HMACGenerator.class" />
+-->
+        <delete file="tmp/org/bouncycastle/" />
+        <!-- lots of unneeded stuff could be deleted here -->
+        <jar destfile="${external-libs-folder}/i2p.jar" basedir="tmp/" />
+
+        <!-- some resources -->
+        <mkdir dir="res/drawable/" />
+        <copy file="../installer/resources/themes/console/images/i2plogo.png" todir="res/drawable/" />
+        <copy file="../installer/resources/blocklist.txt" tofile="res/raw/blocklist_txt" />
+    </target>
+
+    <target name="hackcleanup">
+        <delete file="${external-libs-folder}/crypto.jar" />
+    </target>
+
+    <!--
+         ================================================================================
+         From here down copied from SDK platforms/android-1.1/templates/android_rules.xml
+         and then modified
+         ================================================================================
+    -->
+
+    <!--
+        This rules file is meant to be imported by the custom Ant task:
+            com.android.ant.AndroidInitTask
+
+        The following properties are put in place by the importing task:
+            android-jar, android-aidl, aapt, aidl, and dx
+
+        Additionnaly, the task sets up the following classpath reference:
+            android.target.classpath
+        This is used by the compiler task as the boot classpath.
+    -->
+
+    <!-- Custom tasks -->
+    <taskdef name="aaptexec"
+        classname="com.android.ant.AaptExecLoopTask"
+        classpathref="android.antlibs"/>
+
+    <taskdef name="apkbuilder"
+        classname="com.android.ant.ApkBuilderTask"
+        classpathref="android.antlibs"/>
+
+    <!-- Properties -->
+
+    <property name="android-tools" value="${sdk-location}/tools" />
+
+    <!-- Input directories -->
+    <property name="source-folder" value="src" />
+    <property name="gen-folder" value="gen" />
+    <property name="resource-folder" value="res" />
+    <property name="asset-folder" value="assets" />
+    <property name="source-location" value="${basedir}/${source-folder}" />
+
+    <!-- folder for the 3rd party java libraries -->
+    <property name="external-libs-folder" value="libs" />
+
+    <!-- folder for the native libraries -->
+    <property name="native-libs-folder" value="libs" />
+
+    <!-- Output directories -->
+    <property name="gen-folder" value="gen" />
+    <property name="out-folder" value="bin" />
+    <property name="out-classes" value="${out-folder}/classes" />
+    <property name="out-classes-location" value="${basedir}/${out-classes}"/>
+    <!-- out folders for a parent project if this project is an instrumentation project -->
+    <property name="main-out-folder" value="../${out-folder}" />
+    <property name="main-out-classes" value="${main-out-folder}/classes"/>
+
+    <!-- Intermediate files -->
+    <property name="dex-file" value="classes.dex" />
+    <property name="intermediate-dex" value="${out-folder}/${dex-file}" />
+    <!-- dx does not properly support incorrect / or \ based on the platform
+         and Ant cannot convert them because the parameter is not a valid path.
+         Because of this we have to compute different paths depending on the platform. -->
+    <condition property="intermediate-dex-location"
+            value="${basedir}\${intermediate-dex}"
+            else="${basedir}/${intermediate-dex}" >
+        <os family="windows"/>
+    </condition>
+
+    <!-- The final package file to generate -->
+    <property name="out-debug-package" value="${out-folder}/${ant.project.name}-debug.apk"/>
+
+    <!-- Tools -->
+    <condition property="exe" value=".exe" else=""><os family="windows"/></condition>
+    <property name="adb" value="${android-tools}/adb${exe}"/>
+
+    <!-- rules -->
+
+    <!-- Create the output directories if they don't exist yet. -->
+    <target name="dirs">
+        <echo>Creating output directories if needed...</echo>
+        <mkdir dir="${resource-folder}" />
+        <mkdir dir="${external-libs-folder}" />
+        <mkdir dir="${gen-folder}" />
+        <mkdir dir="${out-folder}" />
+        <mkdir dir="${out-classes}" />
+    </target>
+
+    <!-- Generate the R.java file for this project's resources. -->
+    <target name="resource-src" depends="dirs">
+        <echo>Generating R.java / Manifest.java from the resources...</echo>
+        <exec executable="${aapt}" failonerror="true">
+            <arg value="package" />
+            <arg value="-m" />
+            <arg value="-J" />
+            <arg path="${gen-folder}" />
+            <arg value="-M" />
+            <arg path="AndroidManifest.xml" />
+            <arg value="-S" />
+            <arg path="${resource-folder}" />
+            <arg value="-I" />
+            <arg path="${android-jar}" />
+        </exec>
+    </target>
+
+    <!-- Generate java classes from .aidl files. -->
+    <target name="aidl" depends="dirs">
+        <echo>Compiling aidl files into Java classes...</echo>
+        <apply executable="${aidl}" failonerror="true">
+            <arg value="-p${android-aidl}" />
+            <arg value="-I${source-folder}" />
+            <arg value="-o${gen-folder}" />
+            <fileset dir="${source-folder}">
+                <include name="**/*.aidl"/>
+            </fileset>
+        </apply>
+    </target>
+
+    <!-- Compile this project's .java files into .class files. -->
+    <!-- I2P add buildrouter -->
+    <target name="compile" depends="buildrouter, resource-src, aidl">
+        <javac encoding="ascii" target="1.5" debug="true" extdirs=""
+                destdir="${out-classes}"
+                bootclasspathref="android.target.classpath">
+            <src path="${source-folder}" />
+            <src path="${gen-folder}" />
+            <classpath>
+                <fileset dir="${external-libs-folder}" includes="*.jar"/>
+                <pathelement path="${main-out-classes}"/>
+            </classpath>
+         </javac>
+    </target>
+
+    <!-- Convert this project's .class files into .dex files. -->
+    <!-- I2P add hackcleanup -->
+    <target name="dex" depends="compile, hackcleanup">
+        <echo>Converting compiled files and external libraries into ${out-folder}/${dex-file}...</echo>
+        <apply executable="${dx}" failonerror="true" parallel="true">
+            <!-- I2P this is a bad sign that we need this -->
+            <arg value="-JXmx256m" />
+            <arg value="--dex" />
+            <arg value="--output=${intermediate-dex-location}" />
+            <arg path="${out-classes-location}" />
+            <fileset dir="${external-libs-folder}" includes="*.jar"/>
+        </apply>
+    </target>
+
+    <!-- Put the project's resources into the output package file
+         This actually can create multiple resource package in case
+         Some custom apk with specific configuration have been
+         declared in default.properties.
+         -->
+    <target name="package-resources">
+        <echo>Packaging resources</echo>
+        <aaptexec executable="${aapt}"
+                command="package"
+                manifest="AndroidManifest.xml"
+                resources="${resource-folder}"
+                assets="${asset-folder}"
+                androidjar="${android-jar}"
+                outfolder="${out-folder}"
+                basename="${ant.project.name}" />
+    </target>
+
+    <!-- Package the application and sign it with a debug key.
+         This is the default target when building. It is used for debug. -->
+    <target name="debug" depends="dex, package-resources">
+        <apkbuilder
+                outfolder="${out-folder}"
+                basename="${ant.project.name}"
+                signed="true"
+                verbose="false">
+            <file path="${intermediate-dex}" />
+            <sourcefolder path="${source-folder}" />
+            <jarfolder path="${external-libs-folder}" />
+            <nativefolder path="${native-libs-folder}" />
+        </apkbuilder>
+    </target>
+
+    <!-- Package the application without signing it.
+         This allows for the application to be signed later with an official publishing key. -->
+    <target name="release" depends="dex, package-resources">
+        <apkbuilder
+                outfolder="${out-folder}"
+                basename="${ant.project.name}"
+                signed="false"
+                verbose="false">
+            <file path="${intermediate-dex}" />
+            <sourcefolder path="${source-folder}" />
+            <jarfolder path="${external-libs-folder}" />
+            <nativefolder path="${native-libs-folder}" />
+        </apkbuilder>
+        <echo>All generated packages need to be signed with jarsigner before they are published.</echo>
+    </target>
+
+    <!-- Install the package on the default emulator -->
+    <target name="install" depends="debug">
+        <echo>Installing ${out-debug-package} onto default emulator...</echo>
+        <exec executable="${adb}" failonerror="true">
+            <arg value="install" />
+            <arg path="${out-debug-package}" />
+        </exec>
+    </target>
+
+    <target name="reinstall" depends="debug">
+        <echo>Installing ${out-debug-package} onto default emulator...</echo>
+        <exec executable="${adb}" failonerror="true">
+            <arg value="install" />
+            <arg value="-r" />
+            <arg path="${out-debug-package}" />
+        </exec>
+    </target>
+
+    <!-- Uinstall the package from the default emulator -->
+    <target name="uninstall">
+        <echo>Uninstalling ${application-package} from the default emulator...</echo>
+        <exec executable="${adb}" failonerror="true">
+            <arg value="uninstall" />
+            <arg path="${application-package}" />
+        </exec>
+    </target>
+    
+    <target name="help">
+        <!-- displays starts at col 13
+              |13                                                              80| -->
+        <echo>Android Ant Build. Available targets:</echo>
+        <echo>   help:      Displays this help.</echo>
+        <echo>   debug:     Builds the application and sign it with a debug key.</echo>
+        <echo>   release:   Builds the application. The generated apk file must be</echo>
+        <echo>              signed before it is published.</echo>
+        <echo>   install:   Installs the debug package onto a running emulator or</echo>
+        <echo>              device. This can only be used if the application has </echo>
+        <echo>              not yet been installed.</echo>
+        <echo>   reinstall: Installs the debug package on a running emulator or</echo>
+        <echo>              device that already has the application.</echo>
+        <echo>              The signatures must match.</echo>
+        <echo>   uninstall: uninstall the application from a running emulator or</echo>
+        <echo>              device.</echo>
+    </target>
+</project>
diff --git a/android/default.properties b/android/default.properties
new file mode 100644
index 0000000000000000000000000000000000000000..eba5c59ff571bffa59a08378be7cea9d22972eb4
--- /dev/null
+++ b/android/default.properties
@@ -0,0 +1,11 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+# 
+# This file must be checked in Version Control Systems.
+# 
+# To customize properties used by the Ant build system use,
+# "build.properties", and override values to adapt the script to your
+# project structure.
+
+# Project target.
+target=android-2
diff --git a/android/res/layout/main.xml b/android/res/layout/main.xml
new file mode 100644
index 0000000000000000000000000000000000000000..d764115276c1b6c135b8093a48d4adcb1ee4d67d
--- /dev/null
+++ b/android/res/layout/main.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    >
+<TextView  
+    android:layout_width="fill_parent" 
+    android:layout_height="wrap_content" 
+    android:text="Hello World, I2PAndroid"
+    />
+<ImageView  
+    android:layout_width="fill_parent" 
+    android:layout_height="wrap_content" 
+    android:src="@drawable/i2plogo"
+    />
+</LinearLayout>
+
diff --git a/android/res/raw/logger_config b/android/res/raw/logger_config
new file mode 100644
index 0000000000000000000000000000000000000000..2aeabb9f6413cb523a3c58c735da755c24129580
--- /dev/null
+++ b/android/res/raw/logger_config
@@ -0,0 +1,3 @@
+logger.defaultLevel=INFO
+logger.record.net.i2p.router.transport.FIFOBandwidthRefiller=ERROR
+logger.record.net.i2p.stat.Rate=ERROR
diff --git a/android/res/raw/router_config b/android/res/raw/router_config
new file mode 100644
index 0000000000000000000000000000000000000000..cf63ed56a1055ae73bca780fb20a2f4302499d37
--- /dev/null
+++ b/android/res/raw/router_config
@@ -0,0 +1,16 @@
+# initial router.config
+# temp directory
+i2p.dir.temp=/data/data/net.i2p.router/files/tmp
+i2p.dir.pid=/data/data/net.i2p.router/files/tmp
+# save memory
+router.prng.buffers=2
+router.decayingBloomFilterM=20
+stat.full=false
+i2np.udp.maxConnections=30
+# no I2CP
+i2p.dummyClientFacade=true
+# for now
+i2np.ntcp.enable=false
+# not on android
+i2np.upnp.enable=false
+routerconsole.geoip.enable=false
diff --git a/android/res/values/strings.xml b/android/res/values/strings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..983a304b91143c9280066744db4c5523523ae886
--- /dev/null
+++ b/android/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="app_name">I2PAndroid</string>
+</resources>
diff --git a/android/src/net/i2p/router/I2PAndroid.java b/android/src/net/i2p/router/I2PAndroid.java
new file mode 100644
index 0000000000000000000000000000000000000000..ed626639ceeee57d5ab0e08dbfa20c2dab7b252c
--- /dev/null
+++ b/android/src/net/i2p/router/I2PAndroid.java
@@ -0,0 +1,142 @@
+package net.i2p.router;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.os.Bundle;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.util.List;
+
+import net.i2p.router.Router;
+import net.i2p.router.RouterLaunch;
+// import net.i2p.util.NativeBigInteger;
+
+public class I2PAndroid extends Activity
+{
+    static Context _context;
+    private static final String DIR = "/data/data/net.i2p.router/files";
+
+    /** Called when the activity is first created. */
+    @Override
+    public void onCreate(Bundle savedInstanceState)
+    {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+
+        _context = this;  // Activity extends Context
+        debugStuff();
+        initialize();
+        // 300ms per run
+        // 5x slower than java on my server and 50x slower than native on my server
+        // NativeBigInteger.main(null);
+    }
+
+    public void onRestart()
+    {
+        System.err.println("onRestart called");
+        super.onRestart();
+    }
+
+    public void onStart()
+    {
+        System.err.println("onStart called");
+        super.onStart();
+        RouterLaunch.main(null);
+        System.err.println("Router.main finished");
+    }
+
+    public void onResume()
+    {
+        System.err.println("onResume called");
+        super.onResume();
+    }
+
+    public void onPause()
+    {
+        System.err.println("onPause called");
+        super.onPause();
+    }
+
+    public void onStop()
+    {
+        System.err.println("onStop called");
+        super.onStop();
+
+        // from routerconsole ContextHelper
+        List contexts = RouterContext.listContexts();
+        if ( (contexts == null) || (contexts.size() <= 0) ) 
+            throw new IllegalStateException("No contexts. This is usually because the router is either starting up or shutting down.");
+        RouterContext ctx = (RouterContext)contexts.get(0);
+
+        // shutdown() doesn't return so use shutdownGracefully()
+        ctx.router().shutdownGracefully(Router.EXIT_HARD);
+        System.err.println("shutdown complete");
+    }
+
+    public void onDestroy()
+    {
+        System.err.println("onDestroy called");
+        super.onDestroy();
+    }
+
+    public static Context getContext() {
+        return _context;
+    }
+
+    private void debugStuff() {
+        System.err.println("java.io.tmpdir" + ": " + System.getProperty("java.io.tmpdir"));
+        System.err.println("java.vendor" + ": " + System.getProperty("java.vendor"));
+        System.err.println("java.version" + ": " + System.getProperty("java.version"));
+        System.err.println("os.arch" + ": " + System.getProperty("os.arch"));
+        System.err.println("os.name" + ": " + System.getProperty("os.name"));
+        System.err.println("os.version" + ": " + System.getProperty("os.version"));
+        System.err.println("user.dir" + ": " + System.getProperty("user.dir"));
+        System.err.println("user.home" + ": " + System.getProperty("user.home"));
+        System.err.println("user.name" + ": " + System.getProperty("user.name"));
+    }
+
+    private void initialize() {
+        // Until we can edit the router.config on the device,
+        // copy it from the resource every time.
+        // File f = new I2PFile("router.config");
+        // if (!f.exists()) {
+            copyResourceToFile(R.raw.router_config, "router.config");
+            copyResourceToFile(R.raw.logger_config, "logger.config");
+            copyResourceToFile(R.raw.blocklist_txt, "blocklist.txt");
+        // }
+
+        // Set up the locations so Router and WorkingDir can find them
+        System.setProperty("i2p.dir.base", DIR);
+        System.setProperty("i2p.dir.config", DIR);
+        System.setProperty("wrapper.logfile", DIR + "/wrapper.log");
+    }
+
+    private void copyResourceToFile(int resID, String f) {
+        InputStream in = null;
+        FileOutputStream out = null;
+
+        System.err.println("Creating file " + f + " from resource");
+        byte buf[] = new byte[4096];
+        try {
+            // Context methods
+            in = getResources().openRawResource(resID);
+            out = openFileOutput(f, 0);
+            
+            int read = 0;
+            while ( (read = in.read(buf)) != -1)
+                out.write(buf, 0, read);
+            
+        } catch (IOException ioe) {
+        } catch (Resources.NotFoundException nfe) {
+        } finally {
+            if (in != null) try { in.close(); } catch (IOException ioe) {}
+            if (out != null) try { out.close(); } catch (IOException ioe) {}
+        }
+    }
+    
+}
diff --git a/android/src/net/i2p/util/LogWriter.java b/android/src/net/i2p/util/LogWriter.java
new file mode 100644
index 0000000000000000000000000000000000000000..0babfab37be87e8b6212c4833860d953dcb721d5
--- /dev/null
+++ b/android/src/net/i2p/util/LogWriter.java
@@ -0,0 +1,163 @@
+package net.i2p.util;
+
+/*
+ * public domain
+ *
+ */
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+
+/**
+ * bridge to android logging
+ *
+ * @author zzz
+ */
+class LogWriter implements Runnable {
+    private final static long CONFIG_READ_ITERVAL = 10 * 1000;
+    private long _lastReadConfig = 0;
+    private long _numBytesInCurrentFile = 0;
+    private OutputStream _currentOut; // = System.out
+    private int _rotationNum = -1;
+    private String _logFilenamePattern;
+    private File _currentFile;
+    private LogManager _manager;
+
+    private boolean _write;
+    
+    private LogWriter() { // nop
+    }
+
+    public LogWriter(LogManager manager) {
+        _manager = manager;
+    }
+
+    public void stopWriting() {
+        _write = false;
+    }
+    
+    public void run() {
+        _write = true;
+        try {
+            while (_write) {
+                flushRecords();
+                rereadConfig();
+            }
+            System.err.println("Done writing");
+        } catch (Exception e) {
+            System.err.println("Error writing the logs: " + e.getMessage());
+            e.printStackTrace();
+        }
+    }
+
+    public void flushRecords() { flushRecords(true); }
+    public void flushRecords(boolean shouldWait) {
+        try {
+            List records = _manager._removeAll();
+            if (records == null) return;
+            for (int i = 0; i < records.size(); i++) {
+                LogRecord rec = (LogRecord) records.get(i);
+                writeRecord(rec);
+            }
+        } catch (Throwable t) {
+            t.printStackTrace();
+        } finally {
+            if (shouldWait) {
+                try { 
+                    synchronized (this) {
+                        this.wait(10*1000); 
+                    }
+                } catch (InterruptedException ie) { // nop
+                }
+            }
+        }
+    }
+
+    public String currentFile() {
+        return _currentFile != null ? _currentFile.getAbsolutePath() : "uninitialized";
+    }
+    
+    private void rereadConfig() {
+        long now = Clock.getInstance().now();
+        if (now - _lastReadConfig > CONFIG_READ_ITERVAL) {
+            _manager.rereadConfig();
+            _lastReadConfig = now;
+        }
+    }
+
+    private void writeRecord(LogRecord rec) {
+        if (rec.getThrowable() == null)
+            log(rec.getPriority(), rec.getSource(), rec.getSourceName(), rec.getThreadName(), rec.getMessage());
+        else
+            log(rec.getPriority(), rec.getSource(), rec.getSourceName(), rec.getThreadName(), rec.getMessage(), rec.getThrowable());
+    }
+
+    public void log(int priority, Class src, String name, String threadName, String msg) {
+            if (src != null) {
+                String tag = src.getName();
+                int dot = tag.lastIndexOf(".");
+                if (dot >= 0)
+                    tag = tag.substring(dot + 1);
+                android.util.Log.println(toAndroidLevel(priority),
+                                         tag,
+                                         '[' + threadName + "] " + msg);
+            } else if (name != null)
+                android.util.Log.println(toAndroidLevel(priority),
+                                         name,
+                                         '[' + threadName + "] " + msg);
+            else
+                android.util.Log.println(toAndroidLevel(priority),
+                                         threadName, msg);
+    }
+
+    public void log(int priority, Class src, String name, String threadName, String msg, Throwable t) {
+            if (src != null) {
+                String tag = src.getName();
+                int dot = tag.lastIndexOf(".");
+                if (dot >= 0)
+                    tag = tag.substring(dot + 1);
+                android.util.Log.println(toAndroidLevel(priority),
+                                         tag,
+                                         '[' + threadName + "] " + msg +
+                                         ' ' + t.toString() + ' ' + android.util.Log.getStackTraceString(t));
+            } else if (name != null)
+                android.util.Log.println(toAndroidLevel(priority),
+                                         name,
+                                         '[' + threadName + "] " + msg +
+                                         ' ' + t.toString() + ' ' + android.util.Log.getStackTraceString(t));
+            else
+                android.util.Log.println(toAndroidLevel(priority),
+                                         threadName,
+                                         msg + ' ' + t.toString() + ' ' + android.util.Log.getStackTraceString(t));
+    }
+
+    private static int toAndroidLevel(int level) {
+        switch (level) {
+        case Log.DEBUG:
+            return android.util.Log.DEBUG;
+        case Log.INFO:
+            return android.util.Log.INFO;
+        case Log.WARN:
+            return android.util.Log.WARN;
+        case Log.ERROR:
+        case Log.CRIT:
+        default:
+            return android.util.Log.ERROR;
+        }
+    }
+
+    private static final String replace(String pattern, int num) {
+        char c[] = pattern.toCharArray();
+        StringBuffer buf = new StringBuffer();
+        for (int i = 0; i < c.length; i++) {
+            if ( (c[i] != '#') && (c[i] != '@') )
+                buf.append(c[i]);
+            else
+                buf.append(num);
+        }
+        return buf.toString();
+    }
+}
diff --git a/core/java/src/gnu/crypto/prng/AsyncFortunaStandalone.java b/core/java/src/gnu/crypto/prng/AsyncFortunaStandalone.java
index 417d0fc7215588c29ac675492e1a7587ff73a046..504b87d76dbd89af876b40638fb84357a7a01389 100644
--- a/core/java/src/gnu/crypto/prng/AsyncFortunaStandalone.java
+++ b/core/java/src/gnu/crypto/prng/AsyncFortunaStandalone.java
@@ -12,10 +12,11 @@ import net.i2p.util.Log;
  * has been eaten)
  */
 public class AsyncFortunaStandalone extends FortunaStandalone implements Runnable {
-    private static final int BUFFERS = 16;
+    private static final int DEFAULT_BUFFERS = 16;
     private static final int BUFSIZE = 256*1024;
-    private final byte asyncBuffers[][] = new byte[BUFFERS][BUFSIZE];
-    private final int status[] = new int[BUFFERS];
+    private int _bufferCount;
+    private final byte asyncBuffers[][];
+    private final int status[];
     private int nextBuf = 0;
     private I2PAppContext _context;
     private Log _log;
@@ -27,7 +28,10 @@ public class AsyncFortunaStandalone extends FortunaStandalone implements Runnabl
     
     public AsyncFortunaStandalone(I2PAppContext context) {
         super();
-        for (int i = 0; i < BUFFERS; i++)
+        _bufferCount = context.getProperty("router.prng.buffers", DEFAULT_BUFFERS);
+        asyncBuffers = new byte[_bufferCount][BUFSIZE];
+        status = new int[_bufferCount];
+        for (int i = 0; i < _bufferCount; i++)
             status[i] = STATUS_NEED_FILL;
         _context = context;
         context.statManager().createRateStat("prng.bufferWaitTime", "", "Encryption", new long[] { 60*1000, 10*60*1000, 60*60*1000 } );
@@ -80,11 +84,11 @@ public class AsyncFortunaStandalone extends FortunaStandalone implements Runnabl
             status[nextBuf] = STATUS_LIVE;
             int prev=nextBuf-1;
             if (prev<0)
-                prev = BUFFERS-1;
+                prev = _bufferCount-1;
             if (status[prev] == STATUS_LIVE)
                 status[prev] = STATUS_NEED_FILL;
             nextBuf++;
-            if (nextBuf >= BUFFERS)
+            if (nextBuf >= _bufferCount)
                 nextBuf = 0;
             asyncBuffers.notify();
         }
@@ -95,7 +99,7 @@ public class AsyncFortunaStandalone extends FortunaStandalone implements Runnabl
             int toFill = -1;
             try {
                 synchronized (asyncBuffers) {
-                    for (int i = 0; i < BUFFERS; i++) {
+                    for (int i = 0; i < _bufferCount; i++) {
                         if (status[i] == STATUS_NEED_FILL) {
                             status[i] = STATUS_FILLING;
                             toFill = i;
diff --git a/core/java/src/net/i2p/crypto/HMAC256Generator.java b/core/java/src/net/i2p/crypto/HMAC256Generator.java
index 2fcaa7b5ed55fd927fdbe9898cf82e10e9935647..0335d1e7eb0ef6f3edfe96373847548dd4df1622 100644
--- a/core/java/src/net/i2p/crypto/HMAC256Generator.java
+++ b/core/java/src/net/i2p/crypto/HMAC256Generator.java
@@ -7,7 +7,8 @@ import net.i2p.data.Hash;
 import net.i2p.data.SessionKey;
 
 import org.bouncycastle.crypto.Digest;
-import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.crypto.Mac;
+import org.bouncycastle.crypto.macs.I2PHMac;
 
 /**
  * Calculate the HMAC-SHA256 of a key+message.  All the good stuff occurs
@@ -19,15 +20,15 @@ public class HMAC256Generator extends HMACGenerator {
     public HMAC256Generator(I2PAppContext context) { super(context); }
     
     @Override
-    protected HMac acquire() {
+    protected I2PHMac acquire() {
         synchronized (_available) {
             if (_available.size() > 0)
-                return (HMac)_available.remove(0);
+                return (I2PHMac)_available.remove(0);
         }
         // the HMAC is hardcoded to use SHA256 digest size
         // for backwards compatability.  next time we have a backwards
         // incompatible change, we should update this by removing ", 32"
-        return new HMac(new Sha256ForMAC());
+        return new I2PHMac(new Sha256ForMAC());
     }
     
     private class Sha256ForMAC extends Sha256Standalone implements Digest {
diff --git a/core/java/src/net/i2p/crypto/HMACGenerator.java b/core/java/src/net/i2p/crypto/HMACGenerator.java
index 8388590a2944c686907605aa8d6dda2c1096b3a6..b549c88559ebe44b8e7054217a361f524a1f9ae4 100644
--- a/core/java/src/net/i2p/crypto/HMACGenerator.java
+++ b/core/java/src/net/i2p/crypto/HMACGenerator.java
@@ -10,7 +10,8 @@ import net.i2p.data.Hash;
 import net.i2p.data.SessionKey;
 
 import org.bouncycastle.crypto.digests.MD5Digest;
-import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.crypto.Mac;
+import org.bouncycastle.crypto.macs.I2PHMac;
 
 /**
  * Calculate the HMAC-MD5 of a key+message.  All the good stuff occurs
@@ -49,7 +50,7 @@ public class HMACGenerator {
         if ((key == null) || (key.getData() == null) || (data == null))
             throw new NullPointerException("Null arguments for HMAC");
         
-        HMac mac = acquire();
+        I2PHMac mac = acquire();
         mac.init(key.getData());
         mac.update(data, offset, length);
         //byte rv[] = new byte[Hash.HASH_LENGTH];
@@ -73,7 +74,7 @@ public class HMACGenerator {
         if ((key == null) || (key.getData() == null) || (curData == null))
             throw new NullPointerException("Null arguments for HMAC");
         
-        HMac mac = acquire();
+        I2PHMac mac = acquire();
         mac.init(key.getData());
         mac.update(curData, curOffset, curLength);
         byte rv[] = acquireTmp();
@@ -86,17 +87,17 @@ public class HMACGenerator {
         return eq;
     }
     
-    protected HMac acquire() {
+    protected I2PHMac acquire() {
         synchronized (_available) {
             if (_available.size() > 0)
-                return (HMac)_available.remove(0);
+                return (I2PHMac)_available.remove(0);
         }
         // the HMAC is hardcoded to use SHA256 digest size
         // for backwards compatability.  next time we have a backwards
         // incompatible change, we should update this by removing ", 32"
-        return new HMac(new MD5Digest(), 32);
+        return new I2PHMac(new MD5Digest(), 32);
     }
-    private void release(HMac mac) {
+    private void release(Mac mac) {
         synchronized (_available) {
             if (_available.size() < 64)
                 _available.add(mac);
@@ -122,4 +123,4 @@ public class HMACGenerator {
                 _availableTmp.add((Object)tmp);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/core/java/src/net/i2p/util/DecayingBloomFilter.java b/core/java/src/net/i2p/util/DecayingBloomFilter.java
index 164c8e45345fbbcd398406d10707707041ad6a04..95da0a03bdd8e61593b9307c655427402ab73f49 100644
--- a/core/java/src/net/i2p/util/DecayingBloomFilter.java
+++ b/core/java/src/net/i2p/util/DecayingBloomFilter.java
@@ -30,6 +30,7 @@ public class DecayingBloomFilter {
     private boolean _keepDecaying;
     private DecayEvent _decayEvent;
     
+    private static final int DEFAULT_M = 23;
     private static final boolean ALWAYS_MISS = false;
    
     /**
@@ -44,8 +45,12 @@ public class DecayingBloomFilter {
         _context = context;
         _log = context.logManager().getLog(DecayingBloomFilter.class);
         _entryBytes = entryBytes;
-        _current = new BloomSHA1(23, 11); //new BloomSHA1(23, 11);
-        _previous = new BloomSHA1(23, 11); //new BloomSHA1(23, 11);
+        // this is instantiated in four different places, they may have different
+        // requirements, but for now use this as a gross method of memory reduction.
+        // m == 23 => 1MB each BloomSHA1 (4 pairs = 8MB total)
+        int m = context.getProperty("router.decayingBloomFilterM", DEFAULT_M);
+        _current = new BloomSHA1(m, 11); //new BloomSHA1(23, 11);
+        _previous = new BloomSHA1(m, 11); //new BloomSHA1(23, 11);
         _durationMs = durationMs;
         int numExtenders = (32+ (entryBytes-1))/entryBytes - 1;
         if (numExtenders < 0)
diff --git a/core/java/src/org/bouncycastle/crypto/macs/HMac.java b/core/java/src/org/bouncycastle/crypto/macs/I2PHMac.java
similarity index 95%
rename from core/java/src/org/bouncycastle/crypto/macs/HMac.java
rename to core/java/src/org/bouncycastle/crypto/macs/I2PHMac.java
index 7176c8acafbc61cd08706df1c7888e87e5ae955e..a566e8a79936b52bfe131e9d55478d15f5465d32 100644
--- a/core/java/src/org/bouncycastle/crypto/macs/HMac.java
+++ b/core/java/src/org/bouncycastle/crypto/macs/I2PHMac.java
@@ -42,8 +42,12 @@ import org.bouncycastle.crypto.Mac;
  * a frequently used buffer (called on doFinal).  changes released into the public
  * domain in 2005.
  *
+ * This is renamed from HMac because the constructor HMac(digest, sz) does not exist
+ * in the standard bouncycastle library, thus it conflicts in JVMs that contain the
+ * standard library (Android).
+ *
  */
-public class HMac
+public class I2PHMac
 implements Mac
 {
     private final static int BLOCK_LENGTH = 64;
@@ -56,12 +60,12 @@ implements Mac
     private byte[] inputPad = new byte[BLOCK_LENGTH];
     private byte[] outputPad = new byte[BLOCK_LENGTH];
 
-    public HMac(
+    public I2PHMac(
         Digest digest)
     {
         this(digest, digest.getDigestSize()); 
     }
-    public HMac(
+    public I2PHMac(
         Digest digest, int sz)
     {
         this.digest = digest;