diff --git a/src/org/torproject/android/HttpProxy.java b/src/org/torproject/android/HttpProxy.java new file mode 100644 index 00000000..1da7c719 --- /dev/null +++ b/src/org/torproject/android/HttpProxy.java @@ -0,0 +1,639 @@ +package org.torproject.android; +/*
 */
+/*
+ * This is a simple multi-threaded Java proxy server
+ * for HTTP requests (HTTPS doesn't seem to work, because
+ * the CONNECT requests aren't always handled properly).
+ * I implemented the class as a thread so you can call it 
+ * from other programs and kill it, if necessary (by using 
+ * the closeSocket() method).
+ *
+ * We'll call this the 1.1 version of this class. All I 
+ * changed was to separate the HTTP header elements with
+ * \r\n instead of just \n, to comply with the official
+ * HTTP specification.
+ *  
+ * This can be used either as a direct proxy to other
+ * servers, or as a forwarding proxy to another proxy
+ * server. This makes it useful if you want to monitor
+ * traffic going to and from a proxy server (for example,
+ * you can run this on your local machine and set the
+ * fwdServer and fwdPort to a real proxy server, and then
+ * tell your browser to use "localhost" as the proxy, and
+ * you can watch the browser traffic going in and out).
+ *
+ * One limitation of this implementation is that it doesn't 
+ * close the ProxyThread socket if the client disconnects
+ * or the server never responds, so you could end up with
+ * a bunch of loose threads running amuck and waiting for
+ * connections. As a band-aid, you can set the server socket
+ * to timeout after a certain amount of time (use the
+ * setTimeout() method in the ProxyThread class), although
+ * this can cause false timeouts if a remote server is simply
+ * slow to respond.
+ *
+ * Another thing is that it doesn't limit the number of
+ * socket threads it will create, so if you use this on a
+ * really busy machine that processed a bunch of requests,
+ * you may have problems. You should use thread pools if
+ * you're going to try something like this in a "real"
+ * application.
+ *
+ * Note that if you're using the "main" method to run this
+ * by itself and you don't need the debug output, it will
+ * run a bit faster if you pipe the std output to 'nul'.
+ *
+ * You may use this code as you wish, just don't pretend 
+ * that you wrote it yourself, and don't hold me liable for 
+ * anything that it does or doesn't do. If you're feeling 
+ * especially honest, please include a link to nsftools.com
+ * along with the code. Thanks, and good luck.
+ *
+ * Julian Robichaux -- http://www.nsftools.com
+ */
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.lang.reflect.Array;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.UnknownHostException;
+
+import net.sourceforge.jsocks.socks.Socks5Proxy;
+import net.sourceforge.jsocks.socks.SocksSocket;
+
+
+public class HttpProxy extends Thread
+{
+	public static final int DEFAULT_PORT = 8888;
+	
+	private ServerSocket server = null;
+	private int thisPort = DEFAULT_PORT;
+	private String fwdServer = "";
+	private int fwdPort = 0;
+	private int ptTimeout = ProxyThread.DEFAULT_TIMEOUT;
+	private int debugLevel = 1;
+	private PrintStream debugOut = System.out;
+	private boolean keepRunning = true;
+	private boolean doSocks = false;
+	
+	private Socks5Proxy sProxy = null;
+	
+	/**
+	 * @return the doSocks
+	 */
+	public boolean isDoSocks() {
+		return doSocks;
+	}
+
+
+	/**
+	 * @param doSocks the doSocks to set
+	 */
+	public void setDoSocks(boolean doSocks) {
+		this.doSocks = doSocks;
+	}
+
+
+	/* here's a main method, in case you want to run this by itself */
+	public static void main (String args[])
+	{
+		int port = 0;
+		String fwdProxyServer = "";
+		int fwdProxyPort = 0;
+		
+		if (args.length == 0)
+		{
+			System.err.println("USAGE: java jProxy  [ ]");
+			System.err.println("     the port this service listens on");
+			System.err.println("       optional proxy server to forward requests to");
+			System.err.println("        the port that the optional proxy server is on");
+			System.err.println("\nHINT: if you don't want to see all the debug information flying by,");
+			System.err.println("you can pipe the output to a file or to 'nul' using \">\". For example:");
+			System.err.println("  to send output to the file prox.txt: java jProxy 8080 > prox.txt");
+			System.err.println("  to make the output go away: java jProxy 8080 > nul");
+			return;
+		}
+		
+		// get the command-line parameters
+		port = Integer.parseInt(args[0]);
+		if (args.length > 2)
+		{
+			fwdProxyServer = args[1];
+			fwdProxyPort = Integer.parseInt(args[2]);
+		}
+		
+		// create and start the jProxy thread, using a 20 second timeout
+		// value to keep the threads from piling up too much
+		System.err.println("  **  Starting jProxy on port " + port + ". Press CTRL-C to end.  **\n");
+		HttpProxy jp = new HttpProxy(port, fwdProxyServer, fwdProxyPort, 20);
+		jp.setDebug(1, System.out);		// or set the debug level to 2 for tons of output
+		jp.start();
+		
+		// run forever; if you were calling this class from another
+		// program and you wanted to stop the jProxy thread at some
+		// point, you could write a loop that waits for a certain
+		// condition and then calls jProxy.closeSocket() to kill
+		// the running jProxy thread
+		while (true)
+		{
+			try { Thread.sleep(3000); } catch (Exception e) {}
+		}
+		
+		// if we ever had a condition that stopped the loop above,
+		// we'd want to do this to kill the running thread
+		//jp.closeSocket();
+		//return;
+	}
+	
+	
+	/* the proxy server just listens for connections and creates
+	 * a new thread for each connection attempt (the ProxyThread
+	 * class really does all the work)
+	 */
+	public HttpProxy (int port)
+	{
+		thisPort = port;
+		
+		try {
+			sProxy = new Socks5Proxy(TorConstants.IP_LOCALHOST,TorConstants.PORT_SOCKS);
+		} catch (UnknownHostException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+		sProxy.resolveAddrLocally(false);
+		
+		
+	}
+	
+	public HttpProxy (int port, String proxyServer, int proxyPort)
+	{
+		thisPort = port;
+		fwdServer = proxyServer;
+		fwdPort = proxyPort;
+	}
+	
+	public HttpProxy (int port, String proxyServer, int proxyPort, int timeout)
+	{
+		thisPort = port;
+		fwdServer = proxyServer;
+		fwdPort = proxyPort;
+		ptTimeout = timeout;
+	}
+	
+	
+	/* allow the user to decide whether or not to send debug
+	 * output to the console or some other PrintStream
+	 */
+	public void setDebug (int level, PrintStream out)
+	{
+		debugLevel = level;
+		debugOut = out;
+	}
+	
+	
+	/* get the port that we're supposed to be listening on
+	 */
+	public int getPort ()
+	{
+		return thisPort;
+	}
+	 
+	
+	/* return whether or not the socket is currently open
+	 */
+	public boolean isRunning ()
+	{
+		if (server == null)
+			return false;
+		else
+			return true;
+	}
+	 
+	
+	/* closeSocket will close the open ServerSocket; use this
+	 * to halt a running jProxy thread
+	 */
+	public void closeSocket ()
+	{
+		try {
+			keepRunning = false;
+			// close the open server socket
+			server.close();
+			// send it a message to make it stop waiting immediately
+			// (not really necessary)
+			/*Socket s = new Socket("localhost", thisPort);
+			OutputStream os = s.getOutputStream();
+			os.write((byte)0);
+			os.close();
+			s.close();*/
+		}  catch(Exception e)  { 
+			if (debugLevel > 0)
+				debugOut.println(e);
+		}
+		
+		server = null;
+	}
+	
+	
+	public void run()
+	{
+		try {
+			// create a server socket, and loop forever listening for
+			// client connections
+			server = new ServerSocket(thisPort);
+			
+			while (keepRunning)
+			{
+				Socket client = server.accept();
+				ProxyThread t = new ProxyThread(client, doSocks, sProxy);
+				//t.setDebug(debugLevel, debugOut);
+				//t.setTimeout(ptTimeout);
+				t.start();
+			}
+		}  catch (Exception e)  {
+			if (debugLevel > 0)
+				debugOut.println("jProxy Thread error: " + e);
+		}
+		
+		closeSocket();
+	}
+	
+}
+
+
+/* 
+ * The ProxyThread will take an HTTP request from the client
+ * socket and send it to either the server that the client is
+ * trying to contact, or another proxy server
+ */
+class ProxyThread extends Thread
+{
+	private Socket pSocket;
+	private String fwdServer = "";
+	private int fwdPort = 0;
+	private int debugLevel = 0;
+	private PrintStream debugOut = System.out;
+	
+	// the socketTimeout is used to time out the connection to
+	// the remote server after a certain period of inactivity;
+	// the value is in milliseconds -- use zero if you don't want 
+	// a timeout
+	public static final int DEFAULT_TIMEOUT = 20 * 1000;
+	private int socketTimeout = DEFAULT_TIMEOUT;
+	
+	private boolean doSocks = false;
+	
+	private static Socks5Proxy sProxy = null;
+	
+	public ProxyThread(Socket s, boolean doSocks, Socks5Proxy sProxy)
+	{
+		pSocket = s;
+		
+		this.sProxy = sProxy;
+		this.doSocks = doSocks;
+	}
+
+	public ProxyThread(Socket s, String proxy, int port)
+	{
+		pSocket = s;
+		fwdServer = proxy;
+		fwdPort = port;
+	}
+	
+	
+	public void setTimeout (int timeout)
+	{
+		// assume that the user will pass the timeout value
+		// in seconds (because that's just more intuitive)
+		socketTimeout = timeout * 1000;
+	}
+
+
+	public void setDebug (int level, PrintStream out)
+	{
+		debugLevel = level;
+		debugOut = out;
+	}
+
+
+	public void run()
+	{
+		try
+		{
+			long startTime = System.currentTimeMillis();
+			
+			// client streams (make sure you're using streams that use
+			// byte arrays, so things like GIF and JPEG files and file
+			// downloads will transfer properly)
+			BufferedInputStream clientIn = new BufferedInputStream(pSocket.getInputStream());
+			BufferedOutputStream clientOut = new BufferedOutputStream(pSocket.getOutputStream());
+			
+			// the socket to the remote server
+			Socket server = null;
+			
+			// other variables
+			byte[] request = null;
+			byte[] response = null;
+			int requestLength = 0;
+			int responseLength = 0;
+			int pos = -1;
+			StringBuffer host = new StringBuffer("");
+			String hostName = "";
+			int hostPort = 80;
+			
+			// get the header info (the web browser won't disconnect after
+			// it's sent a request, so make sure the waitForDisconnect
+			// parameter is false)
+			request = getHTTPData(clientIn, host, false);
+			requestLength = Array.getLength(request);
+			
+			// separate the host name from the host port, if necessary
+			// (like if it's "servername:8000")
+			hostName = host.toString();
+			pos = hostName.indexOf(":");
+			if (pos > 0)
+			{
+				try { hostPort = Integer.parseInt(hostName.substring(pos + 1)); 
+					}  catch (Exception e)  { }
+				hostName = hostName.substring(0, pos);
+			}
+			
+			// either forward this request to another proxy server or
+			// send it straight to the Host
+			try
+			{
+				
+				if (!doSocks)
+				{
+					if ((fwdServer.length() > 0) && (fwdPort > 0))
+					{
+						server = new Socket(fwdServer, fwdPort);
+					}  else  {
+						server = new Socket(hostName, hostPort);
+					}
+				}
+				else
+				{
+					
+					server =  new SocksSocket(sProxy,hostName, hostPort);
+				}
+				
+			}  catch (Exception e)  {
+				// tell the client there was an error
+				String errMsg = "HTTP/1.0 500\nContent Type: text/plain\n\n" + 
+								"Error connecting to the server:\n" + e + "\n";
+				clientOut.write(errMsg.getBytes(), 0, errMsg.length());
+			}
+			
+			if (server != null)
+			{
+				server.setSoTimeout(socketTimeout);
+				BufferedInputStream serverIn = new BufferedInputStream(server.getInputStream());
+				BufferedOutputStream serverOut = new BufferedOutputStream(server.getOutputStream());
+				
+				// send the request out
+				serverOut.write(request, 0, requestLength);
+				serverOut.flush();
+				
+				// and get the response; if we're not at a debug level that
+				// requires us to return the data in the response, just stream
+				// it back to the client to save ourselves from having to
+				// create and destroy an unnecessary byte array. Also, we
+				// should set the waitForDisconnect parameter to 'true',
+				// because some servers (like Google) don't always set the
+				// Content-Length header field, so we have to listen until
+				// they decide to disconnect (or the connection times out).
+				if (debugLevel > 1)
+				{
+					response = getHTTPData(serverIn, true);
+					responseLength = Array.getLength(response);
+				}  else  {
+					responseLength = streamHTTPData(serverIn, clientOut, true);
+				}
+				
+				serverIn.close();
+				serverOut.close();
+			}
+			
+			// send the response back to the client, if we haven't already
+			if (debugLevel > 1)
+				clientOut.write(response, 0, responseLength);
+			
+			// if the user wants debug info, send them debug info; however,
+			// keep in mind that because we're using threads, the output won't
+			// necessarily be synchronous
+			if (debugLevel > 0)
+			{
+				long endTime = System.currentTimeMillis();
+				debugOut.println("Request from " + pSocket.getInetAddress().getHostAddress() + 
+									" on Port " + pSocket.getLocalPort() + 
+									" to host " + hostName + ":" + hostPort + 
+									"\n  (" + requestLength + " bytes sent, " + 
+									responseLength + " bytes returned, " + 
+									Long.toString(endTime - startTime) + " ms elapsed)");
+				debugOut.flush();
+			}
+			if (debugLevel > 1)
+			{
+				debugOut.println("REQUEST:\n" + (new String(request)));
+				debugOut.println("RESPONSE:\n" + (new String(response)));
+				debugOut.flush();
+			}
+			
+			// close all the client streams so we can listen again
+			clientOut.close();
+			clientIn.close();
+			pSocket.close();
+		}  catch (Exception e)  {
+			if (debugLevel > 0)
+				debugOut.println("Error in ProxyThread: " + e);
+			//e.printStackTrace();
+		}
+
+	}
+	
+	
+	private byte[] getHTTPData (InputStream in, boolean waitForDisconnect)
+	{
+		// get the HTTP data from an InputStream, and return it as
+		// a byte array
+		// the waitForDisconnect parameter tells us what to do in case
+		// the HTTP header doesn't specify the Content-Length of the
+		// transmission
+		StringBuffer foo = new StringBuffer("");
+		return getHTTPData(in, foo, waitForDisconnect);
+	}
+	
+	
+	private byte[] getHTTPData (InputStream in, StringBuffer host, boolean waitForDisconnect)
+	{
+		// get the HTTP data from an InputStream, and return it as
+		// a byte array, and also return the Host entry in the header,
+		// if it's specified -- note that we have to use a StringBuffer
+		// for the 'host' variable, because a String won't return any
+		// information when it's used as a parameter like that
+		ByteArrayOutputStream bs = new ByteArrayOutputStream();
+		streamHTTPData(in, bs, host, waitForDisconnect);
+		return bs.toByteArray();
+	}
+	
+
+	private int streamHTTPData (InputStream in, OutputStream out, boolean waitForDisconnect)
+	{
+		StringBuffer foo = new StringBuffer("");
+		return streamHTTPData(in, out, foo, waitForDisconnect);
+	}
+	
+	private int streamHTTPData (InputStream in, OutputStream out, 
+									StringBuffer host, boolean waitForDisconnect)
+	{
+		// get the HTTP data from an InputStream, and send it to
+		// the designated OutputStream
+		StringBuffer header = new StringBuffer("");
+		String data = "";
+		int responseCode = 200;
+		int contentLength = 0;
+		int pos = -1;
+		int byteCount = 0;
+
+		try
+		{
+			// get the first line of the header, so we know the response code
+			data = readLine(in);
+			if (data != null)
+			{
+				header.append(data + "\r\n");
+				pos = data.indexOf(" ");
+				if ((data.toLowerCase().startsWith("http")) && 
+					(pos >= 0) && (data.indexOf(" ", pos+1) >= 0))
+				{
+					String rcString = data.substring(pos+1, data.indexOf(" ", pos+1));
+					try
+					{
+						responseCode = Integer.parseInt(rcString);
+					}  catch (Exception e)  {
+						if (debugLevel > 0)
+							debugOut.println("Error parsing response code " + rcString);
+					}
+				}
+			}
+			
+			// get the rest of the header info
+			while ((data = readLine(in)) != null)
+			{
+				// the header ends at the first blank line
+				if (data.length() == 0)
+					break;
+				header.append(data + "\r\n");
+				
+				// check for the Host header
+				pos = data.toLowerCase().indexOf("host:");
+				if (pos >= 0)
+				{
+					host.setLength(0);
+					host.append(data.substring(pos + 5).trim());
+				}
+				
+				// check for the Content-Length header
+				pos = data.toLowerCase().indexOf("content-length:");
+				if (pos >= 0)
+					contentLength = Integer.parseInt(data.substring(pos + 15).trim());
+			}
+			
+			// add a blank line to terminate the header info
+			header.append("\r\n");
+			
+			// convert the header to a byte array, and write it to our stream
+			out.write(header.toString().getBytes(), 0, header.length());
+			
+			// if the header indicated that this was not a 200 response,
+			// just return what we've got if there is no Content-Length,
+			// because we may not be getting anything else
+			if ((responseCode != 200) && (contentLength == 0))
+			{
+				out.flush();
+				return header.length();
+			}
+
+			// get the body, if any; we try to use the Content-Length header to
+			// determine how much data we're supposed to be getting, because 
+			// sometimes the client/server won't disconnect after sending us
+			// information...
+			if (contentLength > 0)
+				waitForDisconnect = false;
+			
+			if ((contentLength > 0) || (waitForDisconnect))
+			{
+				try {
+					byte[] buf = new byte[4096];
+					int bytesIn = 0;
+					while ( ((byteCount < contentLength) || (waitForDisconnect)) 
+							&& ((bytesIn = in.read(buf)) >= 0) )
+					{
+						out.write(buf, 0, bytesIn);
+						byteCount += bytesIn;
+					}
+				}  catch (Exception e)  {
+					String errMsg = "Error getting HTTP body: " + e;
+					if (debugLevel > 0)
+						debugOut.println(errMsg);
+					//bs.write(errMsg.getBytes(), 0, errMsg.length());
+				}
+			}
+		}  catch (Exception e)  {
+			if (debugLevel > 0)
+				debugOut.println("Error getting HTTP data: " + e);
+		}
+		
+		//flush the OutputStream and return
+		try  {  out.flush();  }  catch (Exception e)  {}
+		return (header.length() + byteCount);
+	}
+	
+	
+	private String readLine (InputStream in)
+	{
+		// reads a line of text from an InputStream
+		StringBuffer data = new StringBuffer("");
+		int c;
+		
+		try
+		{
+			// if we have nothing to read, just return null
+			in.mark(1);
+			if (in.read() == -1)
+				return null;
+			else
+				in.reset();
+			
+			while ((c = in.read()) >= 0)
+			{
+				// check for an end-of-line character
+				if ((c == 0) || (c == 10) || (c == 13))
+					break;
+				else
+					data.append((char)c);
+			}
+		
+			// deal with the case where the end-of-line terminator is \r\n
+			if (c == 13)
+			{
+				in.mark(1);
+				if (in.read() != 10)
+					in.reset();
+			}
+		}  catch (Exception e)  {
+			if (debugLevel > 0)
+				debugOut.println("Error getting header: " + e);
+		}
+		
+		// and return what we have
+		return data.toString();
+	}
+	
+}
+
diff --git a/src/org/torproject/android/SocksClient.java b/src/org/torproject/android/SocksClient.java
new file mode 100644
index 00000000..188ebc1e
--- /dev/null
+++ b/src/org/torproject/android/SocksClient.java
@@ -0,0 +1,129 @@
+/* Copyright (c) 2009, Nathan Freitas, The Guardian Project - http://openideals.com/guardian */
+/* See LICENSE for licensing information */
+/** SOCKS aware echo client*/
+
+package org.torproject.android;
+
+import java.io.*;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+
+import net.sourceforge.jsocks.socks.*;
+
+
+public class SocksClient implements Runnable {
+  
+   private int port;
+   private InetAddress hostIP;
+
+   private Socket ss;
+   private InputStream in;
+   private OutputStream out;
+
+   private static final int BUF_SIZE = 1024;
+
+   public SocksClient(String host,int port) 
+	  throws IOException,UnknownHostException,SocksException{
+      this.port = port;
+
+      ss = new SocksSocket(host, port);
+      out = ss.getOutputStream();
+      in  = ss.getInputStream();
+      System.out.println("Connected...");
+      System.out.println("TO: "+host+":"+port);
+      System.out.println("ViaProxy: "+ss.getLocalAddress().getHostAddress()
+                                 +":"+ss.getLocalPort());
+
+   }
+
+   public void close()throws IOException{
+     ss.close();
+   }
+   public void send(String s) throws IOException{
+      out.write(s.getBytes());
+   }
+
+   public void run(){
+      byte[] buf = new byte[1024];
+      int bytes_read;
+      try{
+	  while((bytes_read = in.read(buf)) > 0){
+	     System.out.write(buf,0,bytes_read);
+	  }
+      }catch(IOException io_ex){
+	 io_ex.printStackTrace();
+      }
+   }
+
+   public static void usage(){
+      System.err.print(
+      "Usage: java SocksTest host port [socksHost socksPort]\n");
+   }
+
+
+   public static void main(String args[]){
+      int port;
+      String host;
+      int proxyPort;
+      String proxyHost;
+
+      if(args.length > 1 && args.length < 5){
+	 try{
+
+	     host = args[0];
+	     port = Integer.parseInt(args[1]);
+
+	     proxyPort =(args.length > 3)? Integer.parseInt(args[3])	     
+	                                 : TorConstants.PORT_SOCKS;
+
+	     host = args[0];
+	     proxyHost =(args.length > 2)? args[2]
+	                                 : TorConstants.IP_LOCALHOST;
+
+	     Proxy.setDefaultProxy(proxyHost,proxyPort,"KOUKY001");
+	     //Proxy.setDefaultProxy(proxyHost,proxyPort);
+	     InetRange inetRange = new InetRange();
+	     inetRange.add(InetAddress.getByName("localhost"));
+	     Proxy.getDefaultProxy().setDirect(inetRange);
+
+
+	     SocksClient st = new SocksClient(host,port);
+	     Thread thread = new Thread(st);
+	     thread.start();
+
+	     BufferedReader in = new BufferedReader(
+				 new InputStreamReader(System.in));
+             String s;
+
+             s = in.readLine();
+	     while(s != null){
+                st.send(s+"\r\n");
+		//try{
+		   //Thread.currentThread().sleep(10);
+		//}catch(InterruptedException i_ex){
+		//}
+                s = in.readLine();
+	     }
+	     st.close();
+	     System.exit(1);
+
+	 }catch(SocksException s_ex){
+	   System.err.println("SocksException:"+s_ex);
+	   s_ex.printStackTrace();
+	   System.exit(1); 
+	 }catch(IOException io_ex){
+	   io_ex.printStackTrace();
+	   System.exit(1);
+	 }catch(NumberFormatException num_ex){
+	   usage();
+	   num_ex.printStackTrace();
+	   System.exit(1);
+	 }
+
+      }else{
+	usage();
+      }
+   }
+
+}//End of class
diff --git a/src/org/torproject/android/TorBinaryInstaller.java b/src/org/torproject/android/TorBinaryInstaller.java
new file mode 100644
index 00000000..e69af7b1
--- /dev/null
+++ b/src/org/torproject/android/TorBinaryInstaller.java
@@ -0,0 +1,160 @@
+/* Copyright (c) 2009, Nathan Freitas, The Guardian Project - http://openideals.com/guardian */
+/* See LICENSE for licensing information */
+
+package org.torproject.android;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import android.util.Log;
+
+public class TorBinaryInstaller implements TorConstants {
+
+	private final static String LOG_TAG = "Tor";
+
+	
+	public TorBinaryInstaller ()
+	{
+	}
+	
+	/*
+	 * Start the binary installation if the file doesn't exist or is forced
+	 */
+	public void start (boolean force)
+	{
+		boolean binaryExists = new File(TOR_BINARY_INSTALL_PATH).exists();
+		
+		Log.i(LOG_TAG,"Tor binary exists=" + binaryExists);
+		
+		if (!binaryExists || force)
+			installFromZip ();
+		
+	}
+	
+	/*
+	 * Extract the Tor binary from the APK file using ZIP
+	 */
+	private void installFromZip ()
+	{
+		
+		try
+		{
+			ZipFile zip = new ZipFile(APK_PATH);
+	
+			ZipEntry zipen = zip.getEntry(TOR_BINARY_ZIP_KEY);
+			streamToFile(zip.getInputStream(zipen),TOR_BINARY_INSTALL_PATH);
+			
+			zipen = zip.getEntry(TORRC_ZIP_KEY);
+			streamToFile(zip.getInputStream(zipen),TORRC_INSTALL_PATH);
+			
+			zip.close();
+			
+			Log.i(LOG_TAG,"SUCCESS: unzipped tor binary from apk");
+	
+		}
+		catch (IOException ioe)
+		{
+			Log.i(LOG_TAG,"FAIL: unable to unzip tor binary from apk",ioe);
+		
+		}
+	}
+	
+	/*
+	 * Write the inputstream contents to the file
+	 */
+    private static void streamToFile(InputStream stm, String targetFilename)
+
+    {
+
+        FileOutputStream stmOut = null;
+
+        byte[] buffer = new byte[FILE_WRITE_BUFFER_SIZE];
+
+        int bytecount;
+
+       
+        File outFile = new File(targetFilename);
+        
+        try {
+            outFile.createNewFile();
+
+        	stmOut = new FileOutputStream(outFile);
+        }
+
+        catch (java.io.IOException e)
+
+        {
+
+        	Log.i(LOG_TAG,"Error opening output file " + targetFilename,e);
+
+        	return;
+        }
+
+       
+
+        try
+
+        {
+
+            while ((bytecount = stm.read(buffer)) > 0)
+
+            {
+
+                stmOut.write(buffer, 0, bytecount);
+
+            }
+
+            stmOut.close();
+
+        }
+
+        catch (java.io.IOException e)
+
+        {
+
+            Log.i(LOG_TAG,"Error writing output file '" + targetFilename + "': " + e.toString());
+
+            return;
+
+        }
+
+    }
+	
+    //copy the file from inputstream to File output - alternative impl
+	public void copyFile (InputStream is, File outputFile)
+	{
+		
+		try {
+			outputFile.createNewFile();
+			DataOutputStream out = new DataOutputStream(new FileOutputStream(outputFile));
+			DataInputStream in = new DataInputStream(is);
+			
+			int b;
+			byte[] data = new byte[1024];
+			
+			while ((b = in.read(data)) != -1) {
+				out.write(data);
+			}
+			//
+			out.flush();
+			out.close();
+			in.close();
+			// chmod?
+			
+			
+			
+		} catch (IOException ex) {
+			Log.e(LOG_TAG, "error copying binary", ex);
+		}
+
+	}
+	
+	
+
+}
diff --git a/src/org/torproject/android/TorConstants.java b/src/org/torproject/android/TorConstants.java
new file mode 100644
index 00000000..92b34efb
--- /dev/null
+++ b/src/org/torproject/android/TorConstants.java
@@ -0,0 +1,75 @@
+/* Copyright (c) 2009, Nathan Freitas, The Guardian Project - http://openideals.com/guardian */
+/* See LICENSE for licensing information */
+
+package org.torproject.android;
+
+public interface TorConstants {
+
+	//home directory of Android application
+	public final static String TOR_HOME = "/data/data/org.torproject.android/";
+
+	//name of the tor C binary
+	public final static String TOR_BINARY_ASSET_KEY = "tor";	
+	
+	//path to install the Tor binary too
+	public final static String TOR_BINARY_INSTALL_PATH = TOR_HOME + TOR_BINARY_ASSET_KEY;
+	
+	//key of the tor binary in the Zip file
+	public final static String TOR_BINARY_ZIP_KEY = "assets/" + TOR_BINARY_ASSET_KEY;
+	
+	//torrc file name
+	public final static String TORRC_ASSET_KEY = "torrc";
+	
+	//path to install torrc to within the android app data folder
+	public final static String TORRC_INSTALL_PATH = TOR_HOME + TORRC_ASSET_KEY;
+	
+	//key of the torrc file in the Zip file
+	public final static String TORRC_ZIP_KEY = "assets/" + TORRC_ASSET_KEY;
+
+	//where to send the notices log
+	public final static String TOR_LOG_PATH = TOR_HOME + "notices.log";
+	
+	//control port cookie path
+	public final static String TOR_CONTROL_AUTH_COOKIE = TOR_HOME + "data/control_auth_cookie";
+
+	//how to launch tor
+	public final static String TOR_COMMAND_LINE_ARGS = "-f " + TORRC_INSTALL_PATH;
+	
+	//various console cmds
+	public final static String SHELL_CMD_CHMOD = "/system/bin/chmod";
+	public final static String SHELL_CMD_KILL = "/system/bin/kill";
+	public final static String SHELL_CMD_RM = "/system/bin/rm";
+	public final static String SHELL_CMD_PS = "ps";
+	public final static String CHMOD_EXE_VALUE = "777";
+	
+	//path of the installed APK file
+	public final static String APK_PATH = "/data/app/org.torproject.android.apk";
+	
+	//path to check Tor against
+	public final static String URL_TOR_CHECK = "http://check.torproject.org";
+	
+	public final static int FILE_WRITE_BUFFER_SIZE = 2048;
+	
+	//HTTP Proxy server port
+	public final static int PORT_HTTP = 8118; //just like Privoxy!
+	
+	//Socks port client connects to, server is the Tor binary
+	public final static int PORT_SOCKS = 9050;
+	
+	//what is says!
+	public final static String IP_LOCALHOST = "127.0.0.1";
+	public final static int TOR_CONTROL_PORT = 9051;
+	public final static int UPDATE_TIMEOUT = 3000;
+	
+	public final static String DEFAULT_HOME_PAGE = "file:///android_asset/help.html";// "http://check.torproject.org";
+	
+	//status to communicate state
+    public final static int STATUS_OFF = 0;
+    public final static int STATUS_ON = 1;
+    public final static int STATUS_STARTING_UP = 2;
+    public final static int STATUS_SHUTTING_DOWN = 3;
+    
+    //control port 
+    public final static String TOR_CONTROL_PORT_MSG_BOOTSTRAP_DONE = "Bootstrapped 100%";
+    
+}
diff --git a/src/org/torproject/android/TorControlPanel.java b/src/org/torproject/android/TorControlPanel.java
new file mode 100644
index 00000000..546cc00b
--- /dev/null
+++ b/src/org/torproject/android/TorControlPanel.java
@@ -0,0 +1,587 @@
+/* Copyright (c) 2009, Nathan Freitas, The Guardian Project - http://openideals.com/guardian */
+/* See LICENSE for licensing information */
+
+package org.torproject.android;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.List;
+
+
+import net.freehaven.tor.control.EventHandler;
+
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.webkit.JsResult;
+import android.webkit.WebChromeClient;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+public class TorControlPanel extends Activity implements OnClickListener, TorConstants, EventHandler
+{
+	
+	private final static String TAG = "Tor";
+	
+	private static Intent torService = null;
+	
+	private boolean updateLog = false;
+	private boolean updateStatus = false;
+	
+	private TextView lblStatus = null;
+	private ImageView imgStatus = null;
+	private String txtStatus = "";
+	private int torStatus = STATUS_OFF;
+	
+	private Thread threadStatus = null;
+	
+    private WebView mWebView;
+
+	private int currentView = 0;
+	
+    /** Called when the activity is first created. */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+    	setTheme(android.R.style.Theme_Black);
+
+        
+        showMain();
+
+      
+    }
+    
+    
+    public boolean onCreateOptionsMenu(Menu menu) {
+        super.onCreateOptionsMenu(menu);
+        
+        MenuItem mItem = menu.add(0, 1, Menu.NONE, "Home");
+        MenuItem mItem2 = menu.add(0, 2, Menu.NONE, "Settings");
+        MenuItem mItem3 = menu.add(0, 3, Menu.NONE, "Log");
+       MenuItem mItem4 = menu.add(0, 4, Menu.NONE, "Help");
+       
+      mItem.setIcon(R.drawable.ic_menu_home);
+       mItem2.setIcon(R.drawable.ic_menu_register);
+       mItem3.setIcon(R.drawable.ic_menu_reports);
+       mItem4.setIcon(R.drawable.ic_menu_about);
+       
+        return true;
+    }
+    
+    /* (non-Javadoc)
+	 * @see android.app.Activity#onMenuItemSelected(int, android.view.MenuItem)
+	 */
+	@Override
+	public boolean onMenuItemSelected(int featureId, MenuItem item) {
+		
+		super.onMenuItemSelected(featureId, item);
+		
+		if (item.getItemId() == 1)
+		{
+			this.showMain();
+		}
+		else if (item.getItemId() == 2)
+		{
+			this.showSettings();
+		}
+		else if (item.getItemId() == 3)
+		{
+			this.showMessageLog();
+		}
+		else if (item.getItemId() == 4)
+		{
+			this.showWeb(DEFAULT_HOME_PAGE);
+		}
+		
+        return true;
+	}
+	
+	public boolean onKeyDown(int keyCode, KeyEvent event){
+		if(keyCode==KeyEvent.KEYCODE_BACK){
+			if(currentView != R.layout.layout_main){
+					
+					showMain ();
+					
+					return true;
+			}
+			else{
+				return super.onKeyDown(keyCode, event);
+			}
+		}
+	
+		return super.onKeyDown(keyCode, event);
+		
+	}
+ 
+    /* (non-Javadoc)
+	 * @see android.app.Activity#onPause()
+	 */
+	@Override
+	protected void onPause() {
+		// TODO Auto-generated method stub
+		super.onPause();
+		
+		TorService.setStatus(torStatus);
+	}
+
+
+
+
+	/* (non-Javadoc)
+	 * @see android.app.Activity#onResume()
+	 */
+	@Override
+	protected void onResume() {
+		// TODO Auto-generated method stub
+		super.onResume();
+		
+		torStatus = TorService.getStatus();
+		
+		updateStatus ();
+	}
+
+
+
+
+	/* (non-Javadoc)
+	 * @see android.app.Activity#onStart()
+	 */
+	@Override
+	protected void onStart() {
+		// TODO Auto-generated method stub
+		super.onStart();
+		
+		torStatus = TorService.getStatus();
+
+		
+		updateStatus ();
+	}
+
+
+
+
+	/* (non-Javadoc)
+	 * @see android.app.Activity#onStop()
+	 */
+	@Override
+	protected void onStop() {
+		// TODO Auto-generated method stub
+		super.onStop();
+		
+		TorService.setStatus(torStatus);
+	}
+
+
+
+	/*
+	 * Show the main form UI
+	 */
+	private void showMain ()
+    {
+		updateLog = false;
+		updateStatus = true;
+		
+		currentView = R.layout.layout_main;
+    	setContentView(currentView);
+    	
+    	findViewById(R.id.imgStatus).setOnClickListener(this);
+    	
+    	lblStatus = (TextView)findViewById(R.id.lblStatus);
+    	imgStatus = (ImageView)findViewById(R.id.imgStatus);
+    	
+    	updateStatus();
+    }
+	
+	private void showWeb (String url)
+	{
+		
+		
+		currentView =R.layout.layout_web;
+		 setContentView(currentView);
+	        
+	        mWebView = (WebView) findViewById(R.id.webview);
+
+	        WebSettings webSettings = mWebView.getSettings();
+	        webSettings.setSavePassword(false);
+	        webSettings.setSaveFormData(false);
+	        webSettings.setJavaScriptEnabled(true);
+	      
+
+	        mWebView.setWebChromeClient(new MyWebChromeClient());
+	        
+	        mWebView.loadUrl(url);
+	
+		
+	}
+	
+	
+	/*
+	 * Show the message log UI
+	 */
+	private void showMessageLog ()
+	{
+		currentView = R.layout.layout_log;
+		setContentView(currentView);
+		((Button)findViewById(R.id.btnLogClear)).setOnClickListener(this);
+
+		updateStatus = false;
+		updateLog = true;
+		
+		Thread thread = new Thread ()
+		{
+			public void run ()
+			{
+				
+				while (updateLog)
+				{
+					
+					try {
+						Thread.sleep(UPDATE_TIMEOUT);
+					} catch (InterruptedException e) {
+						// TODO Auto-generated catch block
+						e.printStackTrace();
+					}
+					
+					handler.sendEmptyMessage(0);
+				}
+			}
+		};
+		
+		thread.start();
+		
+	}
+	
+	/*
+	 * Load the Tor log and display it in a text field
+	 */
+	private void updateMessageLog ()
+	{
+				
+		TextView tvLog = (TextView)findViewById(R.id.messageLog);
+    	
+		if (tvLog != null)
+		{
+			String output = loadTextFile(TOR_LOG_PATH);
+		
+			tvLog.setText(output);
+		}
+	
+	}
+	
+	/*
+	 * Handle to reload Tor debug log every few seconds while viewing it
+	 */
+	private Handler handler = new Handler() {
+	
+	        @Override
+		    public void handleMessage(Message msg) {
+	
+	        	updateMessageLog ();
+	
+	        }
+	
+	    };
+	    
+	    /*
+		 * Handle to reload Tor debug log every few seconds while viewing it
+		 */
+		private Handler handlerStatus = new Handler() {
+		
+		        @Override
+			    public void handleMessage(Message msg) {
+		
+		        	updateStatus();
+		        	
+		        	// Toast.makeText(this,txtStatus, Toast.LENGTH_SHORT).show();
+		        }
+		
+		    };
+		
+	
+    /*
+     * Load the basic settings application to display torrc
+     * TODO: these needs to be improved into an actual form GUI
+     */
+	private void showSettings ()
+	{
+		updateStatus = false;
+		updateLog = false;
+		
+		currentView = R.layout.layout_settings;
+		setContentView(currentView);
+
+		
+		String output = loadTextFile(TORRC_INSTALL_PATH);
+
+		TextView tvSettings = (TextView)findViewById(R.id.textSettings);
+    	((Button)findViewById(R.id.btnSettingsSave)).setOnClickListener(this);
+		tvSettings.setText(output);
+		
+	}
+	
+	
+    /*
+     * Set the state of the running/not running graphic and label
+     */
+    public void updateStatus ()
+    {
+    	
+    	if (imgStatus != null)
+    	{
+    		
+	    	if (torStatus == STATUS_ON)
+	    	{
+	    		imgStatus.setImageResource(R.drawable.toron);
+	    		lblStatus.setText("ORbot is running\n- touch the bot to stop -");
+	    		updateStatus = false;
+	    	}
+	    	else if (torStatus == STATUS_STARTING_UP)
+	    	{
+	    		imgStatus.setImageResource(R.drawable.torstarting);
+	    		
+	    		lblStatus.setText("ORbot reports:\n\"" + txtStatus + "\"");
+	    		
+	    	
+	    	}
+	    	else if (torStatus == STATUS_SHUTTING_DOWN)
+	    	{
+	    		imgStatus.setImageResource(R.drawable.torstopping);
+	    		lblStatus.setText("ORbot is shutting down\nplease wait...");
+	    		
+	    	}
+	    	else
+	    	{
+	    		imgStatus.setImageResource(R.drawable.toroff);
+	    		lblStatus.setText("ORbot is not running\n- touch the bot to start -");
+	    		updateStatus = false;
+	    	}
+    	}
+    	
+        
+        
+    }
+  
+    /*
+     * (non-Javadoc)
+     * @see android.view.View.OnClickListener#onClick(android.view.View)
+     */
+	public void onClick(View view) {
+		
+		// the start button
+		if (view.getId()==R.id.imgStatus)
+		{
+			//if Tor binary is not running, then start the service up
+			if (TorService.getStatus()==STATUS_OFF)
+			{
+				torStatus = STATUS_STARTING_UP;
+				txtStatus = "Connecting to Tor...";
+				updateStatus();
+				
+				startTorService ();
+				
+				
+			}
+			else
+			{
+				
+				torStatus = STATUS_SHUTTING_DOWN;
+				updateStatus();
+				
+				stopService(torService);
+				
+				torStatus = STATUS_OFF;
+				
+				updateStatus();
+			}
+			
+		}
+		else if (view.getId()==R.id.btnLogClear)
+		{
+		
+			saveTextFile(TOR_LOG_PATH,"");
+		}
+		else if (view.getId()==R.id.btnSettingsSave)
+		{
+		
+			TextView tvSettings = (TextView)findViewById(R.id.textSettings);
+			String newSettings =  tvSettings.getText().toString();
+			saveTextFile(TORRC_INSTALL_PATH, newSettings);
+			
+		}
+		
+		
+	}
+	
+	private void startTorService ()
+	{
+		if (torService == null)
+		{
+			torService = new Intent(this, TorService.class);
+			//torService.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+			TorService.setActivity(this);
+		}
+		
+		startService(torService);
+		
+	
+		
+	}
+	
+	/*
+	 * Load the log file text
+	 */
+	 public static String loadTextFile (String path)
+	    {
+	    	String line = null;
+	    
+	    	StringBuffer out = new StringBuffer();
+	    	
+	    	try {
+		    	BufferedReader reader = new BufferedReader((new FileReader(new File(path))));
+
+				while ((line = reader.readLine()) != null)
+				{
+					out.append(line);
+					out.append('\n');
+					
+				}
+			} catch (IOException e) {
+				// TODO Auto-generated catch block
+				e.printStackTrace();
+			}
+			
+			return out.toString();
+	    	
+	    }
+	 
+
+		/*
+		 * Load the log file text
+		 */
+		 public static boolean saveTextFile (String path, String contents)
+		    {
+			 	
+		    	try {
+		    		
+		    		 FileWriter writer = new FileWriter( path, false );
+                     writer.write( contents );
+                     
+                     writer.close();
+
+                     
+		    		
+		    		return true;
+			    	
+				} catch (IOException e) {
+				//	Log.i(TAG, "error writing file: " + path, e);
+						e.printStackTrace();
+					return false;
+				}
+				
+				
+		    	
+		    }
+	
+
+	@Override
+	public void bandwidthUsed(long read, long written) {
+		Log.i(TAG,"BW Used: read=" + read + " written=" + written);
+		
+	}
+
+
+	@Override
+	public void circuitStatus(String status, String circID, String path) {
+		Log.i(TAG,"CircuitStatus=" + status + ": " + circID);
+		
+	}
+
+
+	@Override
+	public void message(String severity, String msg) {
+		
+        	 // Log.println(priority, tag, msg)("["+severity+"] "+msg);
+              //Toast.makeText(, text, duration)
+        //      Toast.makeText(ACTIVITY, severity + ": " + msg, Toast.LENGTH_SHORT);
+              Log.i(TAG, "[Tor Control Port] " + severity + ": " + msg);
+              
+              if (msg.indexOf(TOR_CONTROL_PORT_MSG_BOOTSTRAP_DONE)!=-1)
+              {
+            	  torStatus = STATUS_ON;
+            	  
+            	  
+            	  
+            	  //setupWebProxy(true);
+
+              }
+              
+      
+              txtStatus = msg;
+              handlerStatus.sendEmptyMessage(0);
+	
+             
+	}
+
+
+	@Override
+	public void newDescriptors(List orList) {
+		// TODO Auto-generated method stub
+		
+	}
+
+
+	@Override
+	public void orConnStatus(String status, String orName) {
+		
+		Log.i(TAG,"OrConnStatus=" + status + ": " + orName);
+		
+	}
+
+
+	@Override
+	public void streamStatus(String status, String streamID, String target) {
+		Log.i(TAG,"StreamStatus=" + status + ": " + streamID);
+		
+	}
+
+
+	@Override
+	public void unrecognized(String type, String msg) {
+		Log.i(TAG,"unrecognized log=" + type + ": " + msg);
+		
+	}
+	 
+	
+	/**
+     * Provides a hook for calling "alert" from javascript. Useful for
+     * debugging your javascript.
+     */
+    final class MyWebChromeClient extends WebChromeClient {
+        @Override
+        public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
+            Log.d(TAG, message);
+            result.confirm();
+            return true;
+        }
+        
+        
+    }
+	
+}
\ No newline at end of file
diff --git a/src/org/torproject/android/TorService.java b/src/org/torproject/android/TorService.java
new file mode 100644
index 00000000..bb1cca97
--- /dev/null
+++ b/src/org/torproject/android/TorService.java
@@ -0,0 +1,617 @@
+/* Copyright (c) 2009, Nathan Freitas, The Guardian Project - http://openideals.com/guardian */
+/* See LICENSE for licensing information */
+
+package org.torproject.android;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.ConnectException;
+import java.net.Socket;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import net.freehaven.tor.control.EventHandler;
+import net.freehaven.tor.control.NullEventHandler;
+import net.freehaven.tor.control.TorControlConnection;
+import net.sourceforge.jsocks.socks.Proxy;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+import android.widget.Toast;
+
+public class TorService extends Service implements TorConstants
+{
+	
+	private static TorControlPanel ACTIVITY = null;
+	
+	private final static String TAG = "TorService";
+	
+	private static HttpProxy webProxy = null;
+	
+	private static int currentStatus = STATUS_OFF;
+	
+	private TorControlConnection conn = null;
+	
+	private Timer timer = new Timer ();
+	private final static int UPDATE_INTERVAL = 60000;
+	
+    /** Called when the activity is first created. */
+    @Override
+    public void onCreate() {
+    	super.onCreate();
+       
+    	Log.i(TAG,"TorService: onCreate");
+    	
+    	 timer.scheduleAtFixedRate(
+    		      new TimerTask() {
+    		        public void run() {
+    		         
+    		        	//do nothing
+    		        //	Log.i(TAG,"TorService: task is running");
+    		        }
+    		      },
+    		      0,
+    		      UPDATE_INTERVAL);
+    	 
+    	 
+    	 int procId = findProcessId(TorConstants.TOR_BINARY_INSTALL_PATH);
+
+ 		if (procId != -1)
+ 		{
+ 			Log.i(TAG,"Found existing Tor process");
+ 			
+ 			try {
+ 				currentStatus = STATUS_STARTING_UP;
+				
+ 				initControlConnection();
+				
+				getTorStatus();
+				
+				if (webProxy != null)
+				{
+					if (webProxy.isRunning())
+					{
+						//do nothing
+						Log.i(TAG, "Web Proxy is already running");
+					}
+					else
+					{
+						//do nothing
+						Log.i(TAG, "killing Web Proxy");
+						webProxy.closeSocket();
+						setupWebProxy(true);
+					}
+				}
+				else  //do something
+				{
+					setupWebProxy(true);
+				}
+				
+				currentStatus = STATUS_ON;
+				
+			} catch (RuntimeException e) {
+				Log.i(TAG,"Unable to connect to existing Tor instance,",e);
+				currentStatus = STATUS_OFF;
+				this.stopTor();
+				
+			} catch (Exception e) {
+				Log.i(TAG,"Unable to connect to existing Tor instance,",e);
+				currentStatus = STATUS_OFF;
+				this.stopTor();
+				
+			}
+ 		}
+    	 
+    }
+    
+
+    /* (non-Javadoc)
+	 * @see android.app.Service#onLowMemory()
+	 */
+	@Override
+	public void onLowMemory() {
+		// TODO Auto-generated method stub
+		super.onLowMemory();
+	}
+
+
+	/* (non-Javadoc)
+	 * @see android.app.Service#onUnbind(android.content.Intent)
+	 */
+	@Override
+	public boolean onUnbind(Intent intent) {
+		// TODO Auto-generated method stub
+		return super.onUnbind(intent);
+	}
+
+
+	public static int getStatus ()
+    {
+    	
+    	return currentStatus;
+    	
+    }
+    
+	public static void setStatus (int newStatus)
+	{
+		currentStatus = newStatus;
+	}
+   
+    
+    /* (non-Javadoc)
+	 * @see android.app.Service#onRebind(android.content.Intent)
+	 */
+	@Override
+	public void onRebind(Intent intent) {
+		// TODO Auto-generated method stub
+		super.onRebind(intent);
+		
+		  Log.i(TAG,"on rebind");
+	}
+
+
+	/* (non-Javadoc)
+	 * @see android.app.Service#onStart(android.content.Intent, int)
+	 */
+	@Override
+	public void onStart(Intent intent, int startId) {
+		// TODO Auto-generated method stub
+		super.onStart(intent, startId);
+		
+	     Log.i(TAG,"onStart called");
+	     
+		   initTor();
+		   
+		   setupWebProxy (true);
+	}
+
+	
+    public void onDestroy ()
+    {
+    	super.onDestroy();
+    	
+    	Log.i(TAG,"onDestroy called");
+	     
+    	if (timer != null) timer.cancel();
+
+    	stopTor();
+    }
+    
+    private void stopTor ()
+    {
+    	currentStatus = STATUS_SHUTTING_DOWN;
+    	
+    	setupWebProxy(false);  
+    			
+		killTorProcess ();
+				
+		currentStatus = STATUS_OFF;
+    	
+    }
+    
+ 
+    public static void setActivity(TorControlPanel activity) {
+    	ACTIVITY = activity;
+    }
+   
+    private void setupWebProxy (boolean enabled)
+    {
+    	if (enabled)
+    	{
+    		
+    		if (webProxy != null)
+    		{
+    			webProxy.closeSocket();
+    			webProxy = null;
+    			
+    		}
+    		
+	    	Log.i(TAG,"Starting up Web Proxy on port: " + PORT_HTTP);
+	    	//httpd s
+	    	webProxy = new HttpProxy(PORT_HTTP);
+	    	webProxy.setDoSocks(true);
+	    	webProxy.start();
+	    	
+	    	//socks
+	    	try
+	    	{
+	    		
+	    		Proxy.setDefaultProxy(IP_LOCALHOST,PORT_SOCKS);
+	    		
+	    		
+	    	}
+	    	catch (Exception e)
+	    	{
+	    		Log.w(TAG,e.getMessage());
+	    	}
+	    	
+	    	Log.i(TAG,"Web Proxy enabled...");
+
+    	
+	    	//Settings.System.putString(getContentResolver(), Settings.System.HTTP_PROXY, proxySetting);//enable proxy
+	    	//	Settings.Secure.putString(getContentResolver(), Settings.Secure.HTTP_PROXY, proxySetting);//enable proxy
+    		
+    	}
+    	else
+    	{
+	    	//Log.i(TAG,"Turning off Socks/Tor routing on Web Proxy");
+
+    		if (webProxy != null)
+    		{
+    			//logNotice("Tor is disabled - browsing is not anonymous!");
+    			//webProxy.setDoSocks(false);
+    			
+    			webProxy.closeSocket();
+    			webProxy = null;
+    			Log.i(TAG,"WebProxy ServerSocket closed");
+    		}
+    	}
+    	
+    }
+    
+    public void reloadConfig ()
+    {
+    	try
+		{
+	    	if (conn == null)
+			{
+				initControlConnection ();
+			}
+		
+			if (conn != null)
+			{
+				 conn.signal("RELOAD");
+			}
+		}
+    	catch (Exception e)
+    	{
+    		Log.i(TAG,"Unable to reload configuration",e);
+    	}
+    }
+    
+    private void killTorProcess ()
+    {
+		
+    	if (conn != null)
+		{
+			try {
+				Log.i(TAG,"sending SHUTDOWN signal");
+				conn.signal("SHUTDOWN");
+			} catch (IOException e) {
+				// TODO Auto-generated catch block
+				Log.i(TAG,"error shutting down Tor via connection",e);
+			}
+			conn = null;
+		}
+    	
+    	try {
+			Thread.sleep(500);
+		} catch (InterruptedException e) {
+			
+		}
+    	
+		int procId = findProcessId(TorConstants.TOR_BINARY_INSTALL_PATH);
+
+		while (procId != -1)
+		{
+			
+			Log.i(TAG,"Found Tor PID=" + procId + " - killing now...");
+			
+			doCommand(SHELL_CMD_KILL, procId + "");
+
+			procId = findProcessId(TorConstants.TOR_BINARY_INSTALL_PATH);
+		}
+		
+		
+    }
+   
+    private static void logNotice (String msg)
+    {
+
+    	Log.i(TAG, msg);
+		
+    }
+    
+    private void checkBinary ()
+    {
+
+		boolean binaryExists = new File(TOR_BINARY_INSTALL_PATH).exists();
+		
+		if (!binaryExists)
+		{
+			killTorProcess ();
+			
+			TorBinaryInstaller installer = new TorBinaryInstaller(); 
+			installer.start(true);
+		
+			binaryExists = new File(TOR_BINARY_INSTALL_PATH).exists();
+    		if (binaryExists)
+    		{
+    			logNotice("Tor binary installed!");
+    			
+    		}
+    		else
+    		{
+    			logNotice("Tor binary install FAILED!");
+    			return;
+    		}
+		}
+		
+		Log.i(TAG,"Setting permission on Tor binary");
+		doCommand(SHELL_CMD_CHMOD, CHMOD_EXE_VALUE + ' ' + TOR_BINARY_INSTALL_PATH);
+    }
+    
+    public void initTor ()
+    {
+    	try {
+    		
+    		currentStatus = STATUS_STARTING_UP;
+
+    		killTorProcess ();
+    		
+    		checkBinary ();
+    		
+    		doCommand(SHELL_CMD_RM,TOR_LOG_PATH);
+    		
+    		Log.i(TAG,"Starting tor process");
+    		doCommand(TOR_BINARY_INSTALL_PATH, TOR_COMMAND_LINE_ARGS);
+		
+    		int procId = findProcessId(TorConstants.TOR_BINARY_INSTALL_PATH);
+
+    		if (procId == -1)
+    		{
+    			doCommand(TOR_BINARY_INSTALL_PATH, TOR_COMMAND_LINE_ARGS);
+    			procId = findProcessId(TorConstants.TOR_BINARY_INSTALL_PATH);
+    		}
+    		
+    		Log.i(TAG,"Tor process id=" + procId);
+    		
+    		currentStatus = STATUS_STARTING_UP;
+    		logNotice("Tor is starting up...");
+			
+			Thread.sleep(500);
+			initControlConnection ();
+		
+    	} catch (Exception e) {
+			
+			Log.w(TAG,"unable to start Tor Process",e);
+		
+			e.printStackTrace();
+			
+		}
+    	
+    }
+    
+    private static void logStream (InputStream is)
+    {
+    	BufferedReader reader = new BufferedReader(new InputStreamReader(is));
+    	String line = null;
+    
+    	
+    	try {
+			while ((line = reader.readLine()) != null)
+			{
+				Log.i(TAG, line);
+				
+			}
+		} catch (IOException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+		
+    	
+    }
+    
+	
+	
+	@Override
+	public IBinder onBind(Intent arg0) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+	
+	public static int findProcessId(String command) 
+	{
+		int procId = -1;
+		
+		Runtime r = Runtime.getRuntime();
+		    	
+		Process procPs = null;
+		
+        try {
+            
+            procPs = r.exec(SHELL_CMD_PS);
+            
+            BufferedReader reader = new BufferedReader(new InputStreamReader(procPs.getInputStream()));
+            String line = null;
+            
+            while ((line = reader.readLine())!=null)
+            {
+            	if (line.indexOf(command)!=-1)
+            	{
+            		
+            		StringTokenizer st = new StringTokenizer(line," ");
+            		st.nextToken(); //proc owner
+            		
+            		procId = Integer.parseInt(st.nextToken().trim());
+            		
+            		break;
+            	}
+            }
+            
+        } catch (Exception e) {
+            Log.e(TAG, "error: " + e.getMessage(), e);
+        }
+        
+        return procId;
+
+	}
+	
+	public static Process doCommand(String command, String arg1) 
+	{
+		
+		Runtime r = Runtime.getRuntime();
+		    	
+		Process child = null;
+		
+        try {
+            if(child != null) {
+            	child.destroy();
+            	child = null;
+            }
+            
+            child = r.exec(command + ' ' + arg1);
+            
+            
+            
+        } catch (Exception e) {
+            Log.e(TAG, "error: " + e.getMessage());
+        }
+        
+        return child;
+
+	}
+	
+	public static String generateHashPassword ()
+	{
+		/*
+		PasswordDigest d = PasswordDigest.generateDigest();
+	      byte[] s = d.getSecret(); // pass this to authenticate
+	      String h = d.getHashedPassword(); // pass this to the Tor on startup.
+*/
+		return null;
+	}
+	
+	public void initControlConnection () throws Exception, RuntimeException
+	{
+			for (int i = 0; i < 50; i++)
+			{
+				try
+				{
+					Log.i(TAG,"Connecting to control port: " + TOR_CONTROL_PORT);
+					Socket s = new Socket(IP_LOCALHOST, TOR_CONTROL_PORT);
+			        conn = TorControlConnection.getConnection(s);
+			      //  conn.authenticate(new byte[0]); // See section 3.2
+			        
+			        Log.i(TAG,"SUCCESS connected to control port");
+			        
+			        //
+			        File fileCookie = new File(TOR_CONTROL_AUTH_COOKIE);
+			        byte[] cookie = new byte[(int)fileCookie.length()];
+			        new FileInputStream(new File(TOR_CONTROL_AUTH_COOKIE)).read(cookie);
+			        conn.authenticate(cookie);
+			        
+			        Log.i(TAG,"SUCCESS authenticated to control port");
+			        
+			        addEventHandler();
+			        
+			        break; //don't need to retry
+				}
+				catch (ConnectException ce)
+				{
+					Log.i(TAG,"Attempt " + i + ": Error connecting to control port; retrying...");
+					Thread.sleep(1000);
+				}	
+			}
+		
+		
+
+	}
+	
+	public void modifyConf () throws IOException
+	{
+	       // Get one configuration variable.
+	       List options = conn.getConf("contact");
+	       // Get a set of configuration variables.
+	      // List options = conn.getConf(Arrays.asList(new String[]{
+	           //   "contact", "orport", "socksport"}));
+	       // Change a single configuration variable
+	       conn.setConf("BandwidthRate", "1 MB");
+	       // Change several configuration variables
+	       conn.setConf(Arrays.asList(new String[]{
+	              "HiddenServiceDir /home/tor/service1",
+	              "HiddenServicePort 80",
+	       }));
+	       // Reset some variables to their defaults
+	       conn.resetConf(Arrays.asList(new String[]{
+	              "contact", "socksport"
+	       }));
+	       // Flush the configuration to disk.
+	       conn.saveConf();
+
+	}
+	
+	private void getTorStatus () throws IOException
+	{
+		try
+		{
+			 
+			
+			
+		
+			if (conn != null)
+			{
+				 // get a single value.
+			      
+			       // get several values
+			       
+			       if (currentStatus == STATUS_STARTING_UP)
+			       {
+				       //Map vals = conn.getInfo(Arrays.asList(new String[]{
+				         // "status/bootstrap-phase", "status","version"}));
+			
+				       String bsPhase = conn.getInfo("status/bootstrap-phase");
+				    //   Log.i(TAG, "bootstrap-phase: " + bsPhase);
+				       
+				       if (bsPhase.indexOf("PROGRESS=100")!=-1)
+				       {
+				    	   currentStatus = STATUS_ON;
+				       }
+			       }
+			       else
+			       {
+			    	 //  String status = conn.getInfo("status/circuit-established");
+			    	 //  Log.i(TAG, "status/circuit-established=" + status);
+			       }
+			}
+			else
+			{
+				currentStatus = STATUS_OFF;
+			}
+		}
+		catch (Exception e)
+		{
+			Log.i(TAG, "Unable to get Tor status from control port");
+		}
+		
+	}
+	
+	
+	public void addEventHandler () throws IOException
+	{
+	       // We extend NullEventHandler so that we don't need to provide empty
+	       // implementations for all the events we don't care about.
+	       // ...
+        Log.i(TAG,"adding control port event handler");
+
+		conn.setEventHandler(ACTIVITY);
+	    
+		conn.setEvents(Arrays.asList(new String[]{
+	          "ORCONN", "CIRC", "NOTICE", "ERR"}));
+	      // conn.setEvents(Arrays.asList(new String[]{
+	        //  "DEBUG", "INFO", "NOTICE", "WARN", "ERR"}));
+
+	    Log.i(TAG,"SUCCESS added control port event handler");
+
+	}
+}
\ No newline at end of file