diff --git a/tor-android-binary/src/main/java/org/torproject/jni/ClientTransportPluginInterface.java b/tor-android-binary/src/main/java/org/torproject/jni/ClientTransportPluginInterface.java
new file mode 100644
index 0000000000000000000000000000000000000000..ae0cadcfd709813437e3e80024b117a7df0e2078
--- /dev/null
+++ b/tor-android-binary/src/main/java/org/torproject/jni/ClientTransportPluginInterface.java
@@ -0,0 +1,7 @@
+package org.torproject.jni;
+
+public interface ClientTransportPluginInterface {
+    void start();
+    void stop();
+    String getTorrc();
+}
diff --git a/tor-android-binary/src/main/java/org/torproject/jni/TorService.java b/tor-android-binary/src/main/java/org/torproject/jni/TorService.java
index cf97711584c6567c6577bbaac6c117f79a257c75..5af50f6c253b91bf3e9915959ca10d24ba6531d5 100644
--- a/tor-android-binary/src/main/java/org/torproject/jni/TorService.java
+++ b/tor-android-binary/src/main/java/org/torproject/jni/TorService.java
@@ -1,11 +1,21 @@
 package org.torproject.jni;
 
+import static net.freehaven.tor.control.TorControlCommands.EVENT_CELL_STATS;
+import static net.freehaven.tor.control.TorControlCommands.EVENT_CIRCUIT_STATUS;
+import static net.freehaven.tor.control.TorControlCommands.EVENT_CIRCUIT_STATUS_MINOR;
+import static net.freehaven.tor.control.TorControlCommands.EVENT_ERR_MSG;
+import static net.freehaven.tor.control.TorControlCommands.EVENT_NETWORK_LIVENESS;
+import static net.freehaven.tor.control.TorControlCommands.EVENT_NOTICE_MSG;
+import static net.freehaven.tor.control.TorControlCommands.EVENT_WARN_MSG;
+
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Binder;
 import android.os.FileObserver;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.Process;
 import android.util.Log;
 
@@ -13,10 +23,12 @@ import net.freehaven.tor.control.RawEventListener;
 import net.freehaven.tor.control.TorControlCommands;
 import net.freehaven.tor.control.TorControlConnection;
 
+import java.io.BufferedWriter;
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
+import java.io.FileWriter;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -26,6 +38,8 @@ import java.util.Arrays;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.ReentrantLock;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import androidx.annotation.Nullable;
 import androidx.localbroadcastmanager.content.LocalBroadcastManager;
@@ -44,7 +58,7 @@ public class TorService extends Service {
 
     public static final String TAG = "TorService";
 
-    public static final String VERSION_NAME = org.torproject.jni.BuildConfig.VERSION_NAME;
+    //public static final String VERSION_NAME = org.torproject.jni.BuildConfig.VERSION_NAME;
 
     /**
      * Request to transparently start Tor services.
@@ -54,7 +68,7 @@ public class TorService extends Service {
     /**
      * Internal request to stop Tor services.
      */
-    private static final String ACTION_STOP = "org.torproject.android.intent.action.STOP";
+    public static final String ACTION_STOP = "org.torproject.android.intent.action.STOP";
 
     /**
      * {@link Intent} sent by this app with {@code ON/OFF/STARTING/STOPPING} status
@@ -90,6 +104,16 @@ public class TorService extends Service {
      */
     public final static String EXTRA_SERVICE_PACKAGE_NAME = "org.torproject.android.intent.extra.SERVICE_PACKAGE_NAME";
 
+    /**
+     * {@code int} that represents the bootstrapping progress in percent
+     */
+    public static final String EXTRA_STATUS_DETAIL_BOOTSTRAP = "EXTRA_BOOTSTRAP";
+
+    /**
+     * {@code String} that contains a status log description
+     */
+    public static final String EXTRA_STATUS_DETAIL_LOGKEY = "EXTRA_LOG";
+
     /**
      * All tor-related services and daemons are stopped
      */
@@ -101,6 +125,24 @@ public class TorService extends Service {
     public static final String STATUS_STARTING = "STARTING";
     public static final String STATUS_STOPPING = "STOPPING";
 
+    private static final Object LOCK = new Object();
+    private static ClientTransportPluginInterface clientTransportPlugin;
+    private static final Pattern BOOTSTRAP_PATTERN = Pattern.compile("(Bootstrapped )(\\d+)(% \\()(\\w*)(\\): )([\\S|\\s]*)");
+    private static final int GROUP_PERCENT = 2;
+    private static final int GROUP_REASON_KEYWORD = 4;
+
+    public static void setClientTransportPlugin(ClientTransportPluginInterface client) {
+        synchronized (LOCK) {
+            TorService.clientTransportPlugin = client;
+        }
+    }
+
+    public static boolean hasClientTransportPlugin() {
+        synchronized (LOCK) {
+            return clientTransportPlugin != null;
+        }
+    }
+
     /**
      * @return a {@link File} pointing to the location of the optional
      * {@code torrc} file.
@@ -239,21 +281,62 @@ public class TorService extends Service {
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
         sendBroadcastStatusIntent(this);
+        if (intent != null && ACTION_STOP.equals(intent.getAction())) {
+            Log.d(TAG, "stop tor service ACTION_STOP");
+            stopTorService();
+            return START_NOT_STICKY;
+        }
         return super.onStartCommand(intent, flags, startId);
     }
 
+    public void stopTorService() {
+        final Handler handler = new Handler(Looper.getMainLooper());
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                synchronized (LOCK) {
+                    Log.d(TAG, "stopTorService - stopClientTransportPlugin");
+                    if (clientTransportPlugin != null) {
+                        clientTransportPlugin.stop();
+                        clientTransportPlugin = null;
+                    }
+                }
+                handler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        stopSelf();
+                    }
+                });
+            }
+        }).start();
+    }
+
     /**
      * Announce Tor is available for connections once the first circuit is complete
      */
     private final RawEventListener startedEventListener = new RawEventListener() {
         @Override
         public void onEvent(String keyword, String data) {
+            Log.d("[Tor]",  keyword + " " + data);
             if (TorService.STATUS_STARTING.equals(TorService.currentStatus)
-                    && TorControlCommands.EVENT_CIRCUIT_STATUS.equals(keyword)
+                    && EVENT_NOTICE_MSG.equals(keyword)
                     && data != null && data.length() > 0) {
-                String[] tokenArray = data.split(" ");
-                if (tokenArray.length > 1 && TorControlCommands.CIRC_EVENT_BUILT.equals(tokenArray[1])) {
-                    TorService.broadcastStatus(TorService.this, TorService.STATUS_ON);
+                Matcher m = BOOTSTRAP_PATTERN.matcher(data);
+                if (m.matches()) {
+                    String reason = m.group(GROUP_REASON_KEYWORD);
+
+                    Integer percent = -1;
+                    String percentString = m.group(GROUP_PERCENT);
+                    try {
+                        percent = Integer.valueOf(percentString);
+                    } catch (NumberFormatException e) {
+                        e.printStackTrace();
+                    }
+
+                    broadcastStatus(TorService.this, currentStatus, percent, reason);
+                    if (percent == 100) {
+                        broadcastStatus(TorService.this, TorService.STATUS_ON);
+                    }
                 }
             }
         }
@@ -290,23 +373,43 @@ public class TorService extends Service {
                     throw new IOException("cannot read " + controlSocket);
                 }
 
-                FileDescriptor controlSocketFd = prepareFileDescriptor(getControlSocket(TorService.this).getAbsolutePath());
+                int attempts = 0;
+                FileDescriptor controlSocketFd = null;
+                while (controlSocketFd == null && attempts <= 5) {
+                    try {
+                        controlSocketFd = prepareFileDescriptor(getControlSocket(TorService.this).getAbsolutePath());
+                    } catch (Exception e) {
+                        e.printStackTrace();
+                        if (attempts == 5) {
+                            throw new IOException("Failed to prepare control socket file descriptor.");
+                        }
+                        Thread.sleep(200);
+                        attempts++;
+                    }
+                }
                 InputStream is = new FileInputStream(controlSocketFd);
                 OutputStream os = new FileOutputStream(controlSocketFd);
                 torControlConnection = new TorControlConnection(is, os);
                 torControlConnection.launchThread(true);
                 torControlConnection.authenticate(new byte[0]);
                 torControlConnection.addRawEventListener(startedEventListener);
-                torControlConnection.setEvents(Arrays.asList(TorControlCommands.EVENT_CIRCUIT_STATUS));
+                torControlConnection.setEvents(Arrays.asList(EVENT_CIRCUIT_STATUS,
+                        EVENT_CIRCUIT_STATUS_MINOR,
+                        EVENT_NOTICE_MSG,
+                        EVENT_WARN_MSG,
+                        EVENT_ERR_MSG,
+                        EVENT_NETWORK_LIVENESS,
+                        EVENT_CELL_STATS
+                ));
 
                 socksPort = getPortFromGetInfo("net/listeners/socks");
                 httpTunnelPort = getPortFromGetInfo("net/listeners/httptunnel");
 
-            } catch (IOException | ArrayIndexOutOfBoundsException | InterruptedException e) {
+            } catch (IOException | ArrayIndexOutOfBoundsException | InterruptedException | NullPointerException e) {
                 e.printStackTrace();
                 broadcastError(TorService.this, e);
-                broadcastStatus(TorService.this, STATUS_STOPPING);
-                stopSelf();
+                Log.d(TAG, "tor finishing ControlThread");
+                stopTorService();
             }
         }
     };
@@ -318,6 +421,13 @@ public class TorService extends Service {
         public void run() {
             final Context context = getApplicationContext();
             try {
+                synchronized (LOCK) {
+                    if (clientTransportPlugin != null) {
+                        clientTransportPlugin.start();
+                        initTorrc();
+                    }
+                }
+
                 String socksPort = "auto";
                 if (isPortAvailable(9050)) {
                     socksPort = Integer.toString(9050);
@@ -326,7 +436,7 @@ public class TorService extends Service {
                 if (isPortAvailable(8118)) {
                     httpTunnelPort = Integer.toString(8118);
                 }
-
+                runLock.lock();
                 createTorConfiguration();
                 ArrayList<String> lines = new ArrayList<>(Arrays.asList("tor", "--verify-config", // must always be here
                         "--RunAsDaemon", "0",
@@ -376,15 +486,15 @@ public class TorService extends Service {
             } catch (IllegalStateException | IllegalArgumentException | InterruptedException e) {
                 e.printStackTrace();
                 broadcastError(context, e);
+                stopTorService();
             } finally {
-                broadcastStatus(context, STATUS_STOPPING);
                 mainConfigurationFree();
-                TorService.this.stopSelf();
+                runLock.unlock();
             }
         }
     };
 
-    private int getPortFromGetInfo(String key) {
+    private int getPortFromGetInfo(String key) throws NullPointerException {
         final String value = getInfo(key);
         return Integer.parseInt(value.substring(value.lastIndexOf(':') + 1, value.length() - 1));
     }
@@ -406,25 +516,24 @@ public class TorService extends Service {
      * @see <a href="https://github.com/torproject/tor/blob/40be20d542a83359ea480bbaa28380b4137c88b2/src/app/config/config.c#L4730">options that must be on the command line</a>
      */
     private void startTorServiceThread() {
-        if (runLock.isLocked()) {
-            Log.i(TAG, "Waiting for lock");
-        }
-        runLock.lock();
-        Log.i(TAG, "Acquired lock");
         torThread.start();
     }
 
     @Override
     public void onDestroy() {
         super.onDestroy();
+        Log.d(TAG, "onDestroy is called");
         if (torControlConnection != null) {
             torControlConnection.removeRawEventListener(startedEventListener);
         }
-        if (runLock.isLocked()) {
-            Log.i(TAG, "Releasing lock");
-            runLock.unlock();
-        }
+        broadcastStatus(TorService.this, STATUS_STOPPING);
         shutdownTor(TorControlCommands.SIGNAL_SHUTDOWN);
+        synchronized (LOCK) {
+            if (clientTransportPlugin != null) {
+                clientTransportPlugin.stop();
+                clientTransportPlugin = null;
+            }
+        }
         broadcastStatus(TorService.this, STATUS_OFF);
     }
 
@@ -494,6 +603,17 @@ public class TorService extends Service {
         context.sendBroadcast(intent);
     }
 
+    /**
+     * Sends the current status both internally and for any apps that need to
+     * follow the status of TorService.
+     */
+    static void broadcastStatus(Context context, String currentStatus, int percent, @Nullable String logkey) {
+        TorService.currentStatus = currentStatus;
+        Intent intent = getBroadcastIntent(ACTION_STATUS, currentStatus, percent, logkey);
+        LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
+        context.sendBroadcast(intent);
+    }
+
     /**
      * This might be better handled by {@link android.content.ServiceConnection}
      * but there is no way to write tests for {@code ServiceConnection}.
@@ -517,6 +637,15 @@ public class TorService extends Service {
         return intent;
     }
 
+    private static Intent getBroadcastIntent(String action, String currentStatus, int bootstapping, @Nullable String logkey) {
+        Intent intent = new Intent(action);
+        intent.putExtra(EXTRA_STATUS, currentStatus);
+        intent.putExtra(EXTRA_STATUS_DETAIL_BOOTSTRAP, bootstapping);
+        intent.putExtra(EXTRA_STATUS_DETAIL_LOGKEY, logkey);
+        return intent;
+    }
+
+
     private static boolean isPortAvailable(int port) {
         try {
             (new ServerSocket(port)).close();
@@ -526,4 +655,31 @@ public class TorService extends Service {
             return false;
         }
     }
+
+    public void initTorrc() {
+        if (clientTransportPlugin == null) {
+            return;
+        }
+        File torrc = TorService.getTorrc(this);
+        BufferedWriter fileWriter = null;
+
+        String config = clientTransportPlugin.getTorrc();
+
+        try {
+            fileWriter = new BufferedWriter(new FileWriter(torrc));
+            fileWriter.write(config);
+            fileWriter.flush();
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            if (fileWriter != null) {
+                try {
+                    fileWriter.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
 }