Tag Archives: usb

A simple java socket client

This is part two of my example of communicating between android and a pc over usb.
This shows a simple java client based on this oracle tutorial and I use it to test communication between android and the PC.

Here’s the code:

package com.rga.vzw;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Date;

public class EchoClient {
	public static void main(String[] args) throws IOException {

		System.out.println("EchoClient.main()");

		Socket echoSocket = null;
		PrintStream out = null;
		BufferedReader in = null;

		try {
			echoSocket = new Socket("localhost", 38300);
			out = new PrintStream(echoSocket.getOutputStream());
			in = new BufferedReader(new InputStreamReader(
					echoSocket.getInputStream()));
		} catch (UnknownHostException e) {
			System.err.println("Don't know about host: localhost.");
			System.exit(1);
		} catch (IOException e) {
			System.err.println("Couldn't get I/O for "
					+ "the connection to: localhost.");
			System.exit(1);
		}

		BufferedReader stdIn = new BufferedReader(new InputStreamReader(
				System.in));
		// String userInput;
		System.out.println("connected!!");
		int counter = 0;

		// TODO monitor
		while (true) {
			counter++;
			// out.println(counter);
			if (counter % 1000 == 0) {
				out.println("update" + new Date().getSeconds());
				counter = 1;
				System.out.println("echo: " + in.readLine());
			}
		}
	}
}

Connecting android to the pc over usb

*Update* There is some new documentation on the android dev guide. I suggest you start there.

http://developer.android.com/guide/topics/usb/index.html

This will be a two part mini tutorial on how to establish device to host computer communication through usb by using adb. A lot of this is based on Alex Florescu’s work on the subject. My thanks go out to him.

You can read his post here

In part 2, I’ll post a super simple java client to demonstrate the communication.

In short, the way it works is through port-forwarded tcp sockets. This technique sets up a socket server on the Device and a client socket on the host computer. Port forwarding to the device is achieved via adb forward. More on this later.

Ok so let’s get down to it.

After you’ve written both your server and your client apps these are the steps you need to take to get this to work:

  • Launch the server app on the device.
  • Make sure you forward the ports on adb (you only need to do this once)
  • Launch the client app on the host machine

3 steps! that’s it! it’s not that hard at all! Now let’s cover each step in detail.

Let’s start with the server app on the device.

The server socket needs to run on the phone. If you think about it, it makes perfect sense.

The device can’t really know that the host it’s connected to exists. ADB forwards tcp requests FROM the host computer to the device. ADB can’t do that the other way around. This means that only the host computer can initiate a connection. This makes knowing where the server goes pretty obvious.

Don’t worry though, once the connection is made communication can happen bi-directionally.

The first thing to do is to set up a socket connection:

The code below is based on Jason Wei’s awesome post over on think android. My thanks to him

http://thinkandroid.wordpress.com/2010/03/27/incorporating-socket-programming-into-your-applications/

In the code block above we create a new connection to ‘localhost’ and set up a tcp port of our choosing. Any port works, just stay away from the standard 80 or 21.

The connection is wrapped inside a try/catch to allow for a connection timeout.

With a timeout set the server is now open to accept a client connection.

client = server.accept();

Once the connection is established we can get a reference to our I/O. We store this in a global static for easy reference.

Globals.socketIn = new Scanner(client.getInputStream());
				Globals.socketOut = new PrintWriter(client.getOutputStream(),

After the connection is established we can now being to work with the input and output streams.

In the next code block we send some messaging to the ui thread and monitor any input coming from the client.

if (client != null) {
				Globals.connected = true;
				// print out success
				connectionStatus = "Connection was succesful!";
                               //handler out to the ui thread
				mHandler.post(showConnectionStatus);
                                //monitor input fro the client input
				while (Globals.socketIn.hasNext()) {
					socketData = Globals.socketIn.next();
                                        //update the ui thread
					mHandler.post(socketStatus);
				}

			}

And that’s it! now that the connection is set up and we have access to the I/O we can do some work with it. In this example we do some simple ui buttons to test the connection.

Below is a simple click handler for some buttons.

public void onClick(View v) {
		switch (v.getId()) {
		case R.id.connect_button:
			tv = (TextView) findViewById(R.id.connection_text);
			// initialize server socket in a new separate thread
			new Thread(initializeConnection).start();
			String msg = "Attempting to connect...";
			Toast.makeText(this, msg, msg.length()).show();
			break;
		case R.id.hdmi_on:
			Log.d(TAG, "disconnect" + Globals.socketOut);
			if (Globals.socketOut != null) {
				Globals.socketOut.println("hdmiOn");
				Globals.socketOut.flush();
			}
			break;
		case R.id.hdmi_off:
			Log.d(TAG, "disconnect" + Globals.socketOut);
			if (Globals.socketOut != null) {
				Globals.socketOut.println("hdmiOff!!");
				Globals.socketOut.flush();
			}
			break;
		}
	}

Here is what the code for the Server socket on the device looks like.

Keep in mind that this is a very simplistic example. But it should work.

As always, there are many (and better) ways to do things. This is the way that helped me understand what’s going on.

package com.qtcstation;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.Scanner;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;
import android.widget.Toast;

public class SocketMain extends Activity implements OnClickListener {

	public static final String TAG = "Connection";
	public static final int TIMEOUT = 10;
	Intent i = null;
	TextView tv = null;
	private String connectionStatus = null;
	private String socketData = null;
	private Handler mHandler = null;
	ServerSocket server = null;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		// Set up click listeners for the buttons
		View connectButton = findViewById(R.id.connect_button);
		connectButton.setOnClickListener(this);
		View onBtn = findViewById(R.id.hdmi_on);
		onBtn.setOnClickListener(this);
		View offBtn = findViewById(R.id.hdmi_off);
		offBtn.setOnClickListener(this);

		// i = new Intent(this, Connected.class);
		mHandler = new Handler();
	}

	public void onClick(View v) {
		switch (v.getId()) {
		case R.id.connect_button:
			tv = (TextView) findViewById(R.id.connection_text);
			// initialize server socket in a new separate thread
			new Thread(initializeConnection).start();
			String msg = "Attempting to connect...";
			Toast.makeText(this, msg, msg.length()).show();
			break;
		case R.id.hdmi_on:
			Log.d(TAG, "disconnect" + Globals.socketOut);
			if (Globals.socketOut != null) {
				Globals.socketOut.println("hdmiOn");
				Globals.socketOut.flush();
			}
			break;
		case R.id.hdmi_off:
			Log.d(TAG, "disconnect" + Globals.socketOut);
			if (Globals.socketOut != null) {
				Globals.socketOut.println("hdmiOff!!");
				Globals.socketOut.flush();
			}
			break;
		}
	}

	private Runnable initializeConnection = new Thread() {
		public void run() {

			Socket client = null;
			// initialize server socket
			try {
				server = new ServerSocket(38300);
				server.setSoTimeout(TIMEOUT * 1000);

				// attempt to accept a connection
				client = server.accept();
				Globals.socketIn = new Scanner(client.getInputStream());
				Globals.socketOut = new PrintWriter(client.getOutputStream(),
						true);

				// Globals.socketIn.
			} catch (SocketTimeoutException e) {
				// print out TIMEOUT
				connectionStatus = "Connection has timed out! Please try again";
				mHandler.post(showConnectionStatus);
			} catch (IOException e) {
				Log.e(TAG, "" + e);
			} finally {
				// close the server socket
				try {
					if (server != null)
						server.close();
				} catch (IOException ec) {
					Log.e(TAG, "Cannot close server socket" + ec);
				}
			}

			if (client != null) {
				Globals.connected = true;
				// print out success
				connectionStatus = "Connection was succesful!";
				Log.d(TAG, "connected!");
				mHandler.post(showConnectionStatus);
				while (Globals.socketIn.hasNext()) {
					socketData = Globals.socketIn.next();
					mHandler.post(socketStatus);

				}
				// startActivity(i);
			}
		}
	};

	/**
	 * Pops up a "toast" to indicate the connection status
	 */
	private Runnable showConnectionStatus = new Runnable() {
		public void run() {
			Toast.makeText(getBaseContext(), connectionStatus,
					Toast.LENGTH_SHORT).show();
		}
	};

	private Runnable socketStatus = new Runnable() {

		public void run() {
			TextView tv = (TextView) findViewById(R.id.connection_text);
			tv.setText(socketData);
		}
	};

	public static class Globals {
		public static boolean connected;
		public static Scanner socketIn;
		public static PrintWriter socketOut;
	}
}

UPDATE!

Since this post i’ve updated the “server” code to be more robust now that I understand it better.

I separated the socket stuff into its own service that will run constantly (WARNING: this WILL kill the battery quickly, you’ll need to optimize for your needs, for my needs the device is always plugged in. so power is not a concern to me)

Here is the new service code:

package com.qtcstation;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.List;
import java.util.Scanner;

import android.app.ActivityManager;
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

public class ZeusSocketService extends Service {

	private static final String TAG = "ZeusSocket";
	private final IBinder mBinder = new ZeusBinder();

	private Socket client = null;
	private ServerSocket server;

	// socket stuff

	private HashMap<String, Boolean> hdmiApps;

	@Override
	public void onCreate() {
		super.onCreate();
		hdmiApps = parseFeaturedApps();
		createSocketServer();
	}

	public static class Globals {
		public static boolean connected;
		public static Scanner socketIn;
		public static PrintWriter socketOut;
	}

	private HashMap<String, Boolean> parseFeaturedApps() {
		// TODO parse xml.
		HashMap<String, Boolean> h = new HashMap<String, Boolean>();
		AppListHandler handler = new AppListHandler();
		handler.parseXML("apps.xml");
		if (handler.parseSuccess()) {
			Log.d(TAG, "parse success!");
			h = handler.getAppList();
		} else {
			// failsafe hardcoded
			h.put("com.samsung.android.example.Blocks", true);
			h.put("com.android.mms.ui.ConversationList", true);
		}
		return h;

	}

	// socket server needs to do the following things.
	// accept connections until one is established.
	// restart if a connection is lost.
	private void createSocketServer() {
		if (!Globals.connected) {
			Thread t = new Thread(new ServerThread());
			t.start();

			Log.d(TAG, "INITIALIZING SOCKET");
		} else {
			Log.d(TAG, "ALREADY CONNECTED");
		}
	}

	@Override
	public void onDestroy() {
		super.onDestroy();
	}

	@Override
	public IBinder onBind(Intent intent) {
		return mBinder;
	}

	public class ZeusBinder extends Binder {
		ZeusSocketService getService() {
			return ZeusSocketService.this;
		}
	}

	@Override
	public void onStart(Intent intent, int startId) {
		super.onStart(intent, startId);
		// an alarm invokes this every second
		checkRunningApp();
	}

	private void checkRunningApp() {
		// check which activity is currently running.
		ActivityManager am = (ActivityManager) this
				.getSystemService(ACTIVITY_SERVICE);
		List<ActivityManager.RunningTaskInfo> taskInfo = am.getRunningTasks(1);

		ComponentName componentInfo = taskInfo.get(0).topActivity;

		String className = componentInfo.getClassName();

		int status = (hdmiApps.get(className) == null) ? 0 : 1;

		if (status != currentStatus) {
			String action = (status == 0) ? "hdmi_off" : "hdmi_on";
			trackEvent("status_change", action);
		}
		currentStatus = status;

		if (status == 1) {
			if (currentApp.contentEquals(className)
					|| className.equalsIgnoreCase("com.xx.xx.ScreenWaker")) {
			} else {
				trackPageView(hdmiApps.get(className));
				currentApp = className;
			}
		}
		currentApp = className;

		// Log.d(TAG, "running " + className + " \n status: " + status
		// + " \n current app" + currentApp);

		if (Globals.socketOut != null) {

			Globals.socketOut.print(status);
			Globals.socketOut.flush();
		}

		// TODO peridically check if the xml was updated.

	}

	public class ServerThread implements Runnable {

		public void run() {
			try {
				server = new ServerSocket(38300);
				Log.d(TAG, "waiting for connection");
				while (true) {
					// listen for incoming clients
					Socket client = server.accept();
					try {
						BufferedReader in = new BufferedReader(
								new InputStreamReader(client.getInputStream()));
						String line = null;
						Globals.socketOut = new PrintWriter(
								client.getOutputStream(), true);
						while ((line = in.readLine()) != null) {
							// read here
						}
						// break;
					} catch (Exception e) {
						Log.d(TAG, "lost client");
						e.printStackTrace();
					}
				}

			}
			// }
			catch (Exception e) {
				Log.d(TAG, "error, disconnected");
				e.printStackTrace();
			}
		}
	}

}

Update 2

The AppListHandler reference in the code parses an xml file stored in the sdcard. In there we maintain a list of app package names. When we see one of these apps running, then we do something with it. I decided to drop it into an external xml so it would be easy to update from outside the app. After parsing, I simply drop the list of apps into a hashtable. This list is compared in the checkRunningApp() method of the socket server (which is now updated to show that)

package com.rga.sony;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;

import android.os.Environment;
import android.util.Log;

public class AppListHandler extends DefaultHandler {
	private static final String TAG = "parser";
	private HashMap<String, Boolean> appList;
	private long fileSize;

	public void startElement(String uri, String name, String qName,
			Attributes atts) {
		if (name == "app") {
			appList.put(atts.getValue(0), true);
		}
	}

	public void endElement(String uri, String name, String qName)
			throws SAXException {
	}

	public void characters(char ch[], int start, int length) {
		String chars = (new String(ch).substring(start, start + length));
		Log.d(TAG, "chars :" + chars);
	}

	/**
	 * @param path
	 *            - path to the xml file on the SD card.
	 */
	public void parseXML(String path) {
		try {
			appList = new HashMap<String, Boolean>();
			File file = new File(Environment.getExternalStorageDirectory()
					+ "/download/" + path);

			fileSize = file.length();
			SAXParserFactory spf = SAXParserFactory.newInstance();
			SAXParser sp = spf.newSAXParser();
			XMLReader xr = sp.getXMLReader();
			xr.setContentHandler(this);
			xr.parse(new InputSource(new FileInputStream(file)));
		} catch (IOException e) {
			appList = null;
			Log.e(TAG, e.toString());
		} catch (SAXException e) {
			appList = null;
			Log.e(TAG, e.toString());
		} catch (ParserConfigurationException e) {
			appList = null;
			Log.e(TAG, e.toString());
		}

	}

	public boolean parseSuccess() {
		return appList != null;
	}

	public long getFileSize() {
		return fileSize;
	}

	public HashMap<String, Boolean> getAppList() {
		return appList;
	}

}

Finally here is what the xml looks like

<apps>
<app package="com.sony.android.psone.BootActivity"/>
        <app package="com.sonyericsson.zsystem.jni.ZActivity"/>
        <app package="com.gameloft.android.GAND.GloftAsp6.asphalt6.MyVideoView"/>
        <app package="com.gameloft.android.GAND.GloftAsp6.asphalt6.GLGame"/>
        <app package="com.gameloft.android.GAND.GloftStsq.ML.installer.GameInstaller"/>
       <app package="com.gameloft.android.GAND.GloftStsq.ML.MyVideoView"/>
       <app package="com.gameloft.android.GAND.GloftStsq.ML.GLGame"/>
      <app package="com.ea.tetris.Main"/>
      <app package="com.ea.thesims3.Main"/>
      <app package="com.eamobile.madden10_carrier_WF.Madden10"/>
</apps>

I hope this helps!

further reading:

PART 2 – a simple java socket client

Android USB connection to PC

communicating over the usb cable

Tetherbot, a project that uses the same technique. http://graha.ms/androidproxy/