diff --git a/app/build.gradle b/app/build.gradle
index 1d73fd07a3b2980ff8fd550758d3bf24c0b32c93..290474ed7369eeead6f3d893d9039a8f122604c2 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -33,6 +33,7 @@ android {
 
 dependencies {
     compile project(':routerjars')
+    compile project(':client')
     compile 'com.android.support:support-v4:19.1.0'
     compile 'com.android.support:appcompat-v7:19.1.0'
     compile files('libs/androidplot-core-0.6.0.jar')
diff --git a/client/src/main/java/net/i2p/client/DomainServerSocket.java b/client/src/main/java/net/i2p/client/DomainServerSocket.java
new file mode 100644
index 0000000000000000000000000000000000000000..b1ac6b6f9342fea45df98b4c426a90ed14955d3a
--- /dev/null
+++ b/client/src/main/java/net/i2p/client/DomainServerSocket.java
@@ -0,0 +1,172 @@
+package net.i2p.client;
+
+import android.net.LocalServerSocket;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.nio.channels.ServerSocketChannel;
+
+/**
+ * Bridge to LocalServerSocket.
+ * <p/>
+ * accept() returns a real Socket (a DomainSocket).
+ * <p/>
+ * DomainServerSockets are always bound.
+ * You may not create an unbound DomainServerSocket.
+ * Create this through the DomainSocketFactory.
+ *
+ * @author str4d
+ * @since 0.9.14
+ */
+public class DomainServerSocket extends ServerSocket {
+    private final LocalServerSocket mLocalServerSocket;
+    private final DomainSocketFactory mDomainSocketFactory;
+    private volatile boolean mClosed;
+
+    /**
+     * @throws IOException
+     */
+    public DomainServerSocket(String name, DomainSocketFactory domainSocketFactory) throws IOException {
+        mLocalServerSocket = new LocalServerSocket(name);
+        mDomainSocketFactory = domainSocketFactory;
+    }
+
+    /**
+     * @throws IOException
+     */
+    @Override
+    public Socket accept() throws IOException {
+        return mDomainSocketFactory.createSocket(mLocalServerSocket.accept());
+    }
+
+    /**
+     * @throws UnsupportedOperationException always
+     */
+    @Override
+    public void bind(SocketAddress endpoint) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * @throws UnsupportedOperationException always
+     */
+    @Override
+    public void bind(SocketAddress endpoint, int backlog) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * @throws IOException
+     */
+    @Override
+    public void close() throws IOException {
+        mLocalServerSocket.close();
+        mClosed = true;
+    }
+
+    /**
+     * @return null always
+     */
+    @Override
+    public ServerSocketChannel getChannel() {
+        return null;
+    }
+
+    /**
+     * @return null always
+     */
+    @Override
+    public InetAddress getInetAddress() {
+        return null;
+    }
+
+    /**
+     * @return -1 always
+     */
+    @Override
+    public int getLocalPort() {
+        return -1;
+    }
+
+    /**
+     * @return null always
+     */
+    @Override
+    public SocketAddress getLocalSocketAddress() {
+        return null;
+    }
+
+    /**
+     * @throws UnsupportedOperationException always
+     */
+    @Override
+    public int getReceiveBufferSize() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * @return false always
+     */
+    @Override
+    public boolean getReuseAddress() {
+        return false;
+    }
+
+    /**
+     * @throws UnsupportedOperationException always
+     */
+    @Override
+    public int getSoTimeout() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * @return true always
+     */
+    @Override
+    public boolean isBound() {
+        return true;
+    }
+
+    @Override
+    public boolean isClosed() {
+        return mClosed;
+    }
+
+    /**
+     * Does nothing.
+     */
+    @Override
+    public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
+    }
+
+    /**
+     * Does nothing.
+     */
+    @Override
+    public void setReceiveBufferSize(int size) {
+    }
+
+    /**
+     * Does nothing.
+     */
+    @Override
+    public void setReuseAddress(boolean on) {
+    }
+
+    /**
+     * Does nothing.
+     */
+    @Override
+    public void setSoTimeout(int timeout) throws SocketException {
+    }
+
+    @Override
+    public String toString() {
+        return mLocalServerSocket.toString();
+    }
+}
diff --git a/client/src/main/java/net/i2p/client/DomainSocket.java b/client/src/main/java/net/i2p/client/DomainSocket.java
new file mode 100644
index 0000000000000000000000000000000000000000..0697f477cef58c918b421cb27da680a2b88c7ab2
--- /dev/null
+++ b/client/src/main/java/net/i2p/client/DomainSocket.java
@@ -0,0 +1,363 @@
+package net.i2p.client;
+
+import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.nio.channels.SocketChannel;
+
+/**
+ * Bridge to LocalSocket.
+ * <p/>
+ * DomainSockets are always bound, and always start out connected.
+ * You may not create an unbound DomainSocket.
+ * Create this through the DomainSocketManager.
+ *
+ * @author str4d
+ * @since 0.9.14
+ */
+public class DomainSocket extends Socket {
+    private final LocalSocket mLocalSocket;
+
+    /**
+     * @throws IOException
+     * @throws UnsupportedOperationException always
+     */
+    DomainSocket(String name) throws IOException {
+        mLocalSocket = new LocalSocket();
+        mLocalSocket.connect(new LocalSocketAddress(name));
+    }
+
+    /**
+     * @param localSocket the LocalSocket to wrap.
+     */
+    DomainSocket(LocalSocket localSocket) {
+        mLocalSocket = localSocket;
+    }
+
+    /**
+     * @throws UnsupportedOperationException always
+     */
+    @Override
+    public void bind(SocketAddress bindpoint) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * @throws IOException
+     */
+    @Override
+    public void close() throws IOException {
+        mLocalSocket.close();
+    }
+
+    /**
+     * @throws UnsupportedOperationException always
+     */
+    @Override
+    public void connect(SocketAddress endpoint) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * @throws UnsupportedOperationException always
+     */
+    @Override
+    public void connect(SocketAddress endpoint, int timeout) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * @return null always, unimplemented
+     */
+    @Override
+    public SocketChannel getChannel() {
+        return null;
+    }
+
+    /**
+     * @return null always
+     */
+    @Override
+    public InetAddress getInetAddress() {
+        return null;
+    }
+
+    /**
+     * @throws IOException
+     */
+    @Override
+    public InputStream getInputStream() throws IOException {
+        return mLocalSocket.getInputStream();
+    }
+
+    /**
+     * @throws UnsupportedOperationException always
+     */
+    @Override
+    public boolean getKeepAlive() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * @return null always
+     */
+    @Override
+    public InetAddress getLocalAddress() {
+        return null;
+    }
+
+    /**
+     * @return -1 always
+     */
+    @Override
+    public int getLocalPort() {
+        return -1;
+    }
+
+    /**
+     * @return null always
+     */
+    @Override
+    public SocketAddress getLocalSocketAddress() {
+        return null;
+    }
+
+    /**
+     * @return false always
+     */
+    @Override
+    public boolean getOOBInline() {
+        return false;
+    }
+
+    /**
+     * @throws IOException
+     */
+    @Override
+    public OutputStream getOutputStream() throws IOException {
+        return mLocalSocket.getOutputStream();
+    }
+
+    /**
+     * @return -1 always
+     */
+    @Override
+    public int getPort() {
+        return -1;
+    }
+
+    @Override
+    public int getReceiveBufferSize() throws SocketException {
+        try {
+            return mLocalSocket.getReceiveBufferSize();
+        } catch (IOException e) {
+            throw new SocketException(e.getLocalizedMessage());
+        }
+    }
+
+    /**
+     * @throws UnsupportedOperationException always
+     */
+    @Override
+    public SocketAddress getRemoteSocketAddress() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * @return false always
+     */
+    @Override
+    public boolean getReuseAddress() {
+        return false;
+    }
+
+    /**
+     * @throws SocketException
+     */
+    @Override
+    public int getSendBufferSize() throws SocketException {
+        try {
+            return mLocalSocket.getSendBufferSize();
+        } catch (IOException e) {
+            throw new SocketException(e.getLocalizedMessage());
+        }
+    }
+
+    /**
+     * @throws UnsupportedOperationException always
+     */
+    @Override
+    public int getSoLinger() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * @throws SocketException
+     */
+    @Override
+    public int getSoTimeout() throws SocketException {
+        try {
+            return mLocalSocket.getSoTimeout();
+        } catch (IOException e) {
+            throw new SocketException(e.getLocalizedMessage());
+        }
+    }
+
+    /**
+     * @return false always
+     */
+    @Override
+    public boolean getTcpNoDelay() {
+        return false;
+    }
+
+    /**
+     * @return 0 always
+     */
+    @Override
+    public int getTrafficClass() {
+        return 0;
+    }
+
+    @Override
+    public boolean isBound() {
+        return mLocalSocket.isBound();
+    }
+
+    @Override
+    public boolean isClosed() {
+        return mLocalSocket.isClosed();
+    }
+
+    @Override
+    public boolean isConnected() {
+        return mLocalSocket.isConnected();
+    }
+
+    @Override
+    public boolean isInputShutdown() {
+        return mLocalSocket.isInputShutdown();
+    }
+
+    @Override
+    public boolean isOutputShutdown() {
+        return mLocalSocket.isOutputShutdown();
+    }
+
+    /**
+     * @throws UnsupportedOperationException always
+     */
+    @Override
+    public void sendUrgentData(int data) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Does nothing.
+     */
+    @Override
+    public void setKeepAlive(boolean on) {
+    }
+
+    /**
+     * @throws UnsupportedOperationException if on is true
+     */
+    @Override
+    public void setOOBInline(boolean on) {
+        if (on)
+            throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Does nothing.
+     */
+    @Override
+    public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
+    }
+
+    /**
+     * @throws SocketException
+     */
+    @Override
+    public void setReceiveBufferSize(int size) throws SocketException {
+        try {
+            mLocalSocket.setReceiveBufferSize(size);
+        } catch (IOException e) {
+            throw new SocketException(e.getLocalizedMessage());
+        }
+    }
+
+    /**
+     * Does nothing.
+     */
+    @Override
+    public void setReuseAddress(boolean on) {
+    }
+
+    /**
+     * @throws SocketException
+     */
+    @Override
+    public void setSendBufferSize(int size) throws SocketException {
+        try {
+            mLocalSocket.setSendBufferSize(size);
+        } catch (IOException e) {
+            throw new SocketException(e.getLocalizedMessage());
+        }
+    }
+
+    /**
+     * Does nothing.
+     */
+    @Override
+    public void setSoLinger(boolean on, int linger) {
+    }
+
+    /**
+     * @throws SocketException
+     */
+    @Override
+    public void setSoTimeout(int timeout) throws SocketException {
+        try {
+            mLocalSocket.setSoTimeout(timeout);
+        } catch (IOException e) {
+            throw new SocketException(e.getLocalizedMessage());
+        }
+    }
+
+    /**
+     * Does nothing.
+     */
+    @Override
+    public void setTcpNoDelay(boolean on) {
+    }
+
+    /**
+     * Does nothing.
+     */
+    @Override
+    public void setTrafficClass(int tc) {
+    }
+
+    @Override
+    public void shutdownInput() throws IOException {
+        mLocalSocket.shutdownInput();
+    }
+
+    @Override
+    public void shutdownOutput() throws IOException {
+        mLocalSocket.shutdownOutput();
+    }
+
+    @Override
+    public String toString() {
+        return mLocalSocket.toString();
+    }
+}
diff --git a/client/src/main/java/net/i2p/client/DomainSocketFactory.java b/client/src/main/java/net/i2p/client/DomainSocketFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..511dacc0e67b74cb6de8b0cdb50d151c861c5fa8
--- /dev/null
+++ b/client/src/main/java/net/i2p/client/DomainSocketFactory.java
@@ -0,0 +1,34 @@
+package net.i2p.client;
+
+import android.net.LocalSocket;
+
+import net.i2p.I2PAppContext;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+/**
+ * Bridge to Android implementation of Unix domain sockets.
+ *
+ * @author str4d
+ * @since 0.9.14
+ */
+public class DomainSocketFactory {
+    public static String I2CP_SOCKET_ADDRESS = "net.i2p.android.client.i2cp";
+
+    public DomainSocketFactory(I2PAppContext context) {
+    }
+
+    public Socket createSocket(String name) throws IOException {
+        return new DomainSocket(name);
+    }
+
+    public Socket createSocket(LocalSocket localSocket) {
+        return new DomainSocket(localSocket);
+    }
+
+    public ServerSocket createServerSocket(String name) throws IOException {
+        return new DomainServerSocket(name, this);
+    }
+}
diff --git a/routerjars/build.xml b/routerjars/build.xml
index ac84fc63cdd6a33636a7960529275bcca6f0c14b..9c5f26bfc99eeeee6c943502038ef7942c7d0e40 100644
--- a/routerjars/build.xml
+++ b/routerjars/build.xml
@@ -48,6 +48,7 @@
         <!-- lots of unneeded stuff could be deleted here -->
         <jar destfile="${jar.libs.dir}/i2p.jar" >
             <zipfileset src="${i2pbase}/build/i2p.jar" >
+                <exclude name="net/i2p/client/DomainSocketFactory.class" />
                 <exclude name="net/i2p/util/LogWriter.class" />
             </zipfileset>
         </jar>