/** ** Copyright (C) SAS Institute, All rights reserved. ** General Public License: http://www.opensource.org/licenses/gpl-license.php **/ package org.safs.sockets.android; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import org.safs.android.auto.lib.AndroidTools; import org.safs.android.auto.lib.DUtilities; import org.safs.android.auto.lib.Process2; import org.safs.sockets.AbstractProtocolRunner; import org.safs.sockets.AvailablePortFinder; import org.safs.sockets.NamedListener; import org.safs.sockets.SocketProtocol; /** * This class extends {@link SocketProtocol}, before connect to server<br> * it will try to forward the 'pc local port' to 'device server port' if<br> * the 'port forwarding' is enabled.<br> * * @see SocketProtocol * @see AbstractProtocolRunner */ public class DroidSocketProtocol extends SocketProtocol{ String[] tcpfowardParams = {"forward", "tcp:2411", "tcp:2410"}; static AndroidTools tools = null; public DroidSocketProtocol(){} public DroidSocketProtocol(NamedListener listener){ super(listener); } /** * <pre> * {@link #portForwarding} means if we want to forward local port {@link #controllerPort} to * remote server port {@link #remotePort} * If an emulator or a USB-connected-device is used to run android application, you should * set {@link #portForwarding} to true; * Otherwise, if a WIFI-connected-device is used to run android application, you should * set {@link #portForwarding} to false; * * If we have forwarded, the {@link #controllerPort} will be considered as a server port * to be connected by client controller. * * if true, controller Runner will use {@link #controllerPort} to create connection. * if false, controller Runner will use {@link #remotePort} to create connection. * </pre> */ private boolean portForwarding = true; /** * Override the method in super class.<br> * If we have forwarded the tcp port from {@link #controllerPort} to {@link #remotePort}. * we will use the {@link #controllerPort} to create connection.<br> * Otherwise, we just call {@link super#bindToRemoteServer()}, which use {@link #remotePort} to create connection.<br> * * <p> * Attention:<br> * If port forwarding is true, adb will make a port forward from {@link #controllerPort} to {@link #remotePort} * BUT, adb will NOT check if there is a ServerSocket running on the {@link #remotePort} * After forwarding, the Socket {@link #controllerRunner} can always be created successfully. * So this socket connection is not reliable!!! See {@link #main(String[])}. * If verification fail, retry connection IS NEEDED. * * @see #retryConnectionAfterVerificationFail() */ protected void bindToRemoteServer() throws IOException{ if(portForwarding){ if(forwardPort(controllerPort, remotePort)){ //After forwarding port, should we create the Socket connection always with "localhost"? // controllerRunner = new Socket("localhost", controllerPort); //If remoteHostname is assigned to an other value (not default "localhost"), this may fail. controllerRunner = new Socket(remoteHostname, controllerPort); }else{ debug("Fail to forward from 'local:"+controllerPort+"' to 'device/emulator:"+remotePort+"'"); //throw new IOException("Fail to forward from '"+controllerPort+"' to '"+remotePort+"'"); } }else{ super.bindToRemoteServer(); } } /** * set the value for field {@link #portForwarding}<br> * If the 'port forwarding' is set to true, remember to call {@link #adjustControllerPort()}<br> * to choose an available port for 'controller'.<br> * * @param portForwarding * @see #adjustControllerPort() */ public void setPortForwarding(boolean portForwarding){ this.portForwarding = portForwarding; } /** * get the value of {@link #portForwarding} * @return boolean, portForwarding */ public boolean getPortForwarding(){ return portForwarding; } /** * Use adb to forward local machine's port to a device/emulator's port * * @param localPCPort int, port number of local machine * @param remoteDevicePort int, port number of device/emulator * @return */ public boolean forwardPort(int localPCPort, int remoteDevicePort){ Process2 forwardProcess = null; try { debug("Forwarding port from 'local:"+localPCPort+"' to 'device/emulator:"+remoteDevicePort+"'"); tcpfowardParams[1] = "tcp:"+localPCPort; tcpfowardParams[2] = "tcp:"+remoteDevicePort; if(tools==null) tools = DUtilities.getAndroidTools(null); forwardProcess = tools.adb(DUtilities.addDeviceSerialParam(tcpfowardParams)).forwardOutput().waitForSuccess(); return true; } catch (InterruptedException e) { debug("Forwarding port error: "+e.getMessage()); } catch (IOException e) { debug("Forwarding port error: "+e.getMessage()); } finally{ if(forwardProcess!=null) forwardProcess.destroy(); } return false; } /** * Choose an available port (not occupied by other ServerSocket or DatagramSocket) for controller port.<br> * After calling this, you should NOT call {@link #setControllerPort(int)} unless you know you are setting<br> * an available port for controller port.<br> * * This method should be called after calling {@link #setPortForwarding(boolean)}<br> * * @see #setControllerPort(int) * @see #setPortForwarding(boolean) */ public void adjustControllerPort(){ if(portForwarding){ //verify controllerPort is available, if not, get the next available one. controllerPort = AvailablePortFinder.getNextAvailable(controllerPort); debug("Port forwarding will be from '"+controllerPort+"'"); setControllerPort(controllerPort); } } /** * This method will prove that we can create a connection SUCCESSFULLY with a 'serverPort' where * no SocketServer is running, the precondition is that we use adb to forward that 'serverPort' to * a port on device/emulator (even there is NO SocketServer running on that port on device/emulator) * * This prove that the connection is probably not a real one if we used 'adb port forwarding'. * * @param args */ public static void main(String[] args){ ServerSocket server = null; try { int port = 2411; if(args.length==1){ try{ port = Integer.parseInt(args[0]); }catch(Exception e){} } server = new ServerSocket(port); System.out.println("ServerSocket is running on port "+server.getLocalPort()); try{ //The first connection can be established. Socket client = new Socket("localhost", port); System.out.println("socket connection with server on port "+client.getPort()); }catch(Exception e){ System.err.println("warning: "+e.getMessage()); } //Create a server port(hope there is no Server running) to make a connection test int serverPort = port+2400; try{ //Normally, the following connection will fail except there is a Server running on that port Socket client1 = new Socket("localhost", serverPort); System.out.println("socket connection with server on port "+client1.getPort()); }catch(IOException e){ System.err.println("warning: "+e.getMessage()+": connection can't be established on port "+serverPort); try{ //If comes here, we know there is no server running on 'serverPort' //But if we execute adb forward, the connection can be established on that 'serverPort' //even if we forward to a device port where no SocketServer is running. //This proves that the connection is not reliable. int whatEverPortOnMobileDevice = 2895; Runtime.getRuntime().exec("adb forward tcp:"+serverPort+" tcp:"+whatEverPortOnMobileDevice); Socket client2 = new Socket("localhost", serverPort); System.out.println("socket connection with server on port "+client2.getPort()); }catch(IOException e1){ System.err.println("warning: "+e1.getMessage()); } } }catch (IOException e) { e.printStackTrace(); }finally{ if(server!=null) try { server.close(); } catch (IOException e) { e.printStackTrace(); } } } }