Download as txt, pdf, or txt
Download as txt, pdf, or txt
You are on page 1of 10

// -*- mode: java; c-basic-offset: 2; -*-

// Copyright 2009-2011 Google, All Rights reserved


// Copyright 2011-2012 MIT, All rights reserved
// Released under the Apache License, Version 2.0
// http://www.apache.org/licenses/LICENSE-2.0

package com.gmail.at.moicjarod;

import com.google.appinventor.components.runtime.*;
import com.google.appinventor.components.runtime.util.RuntimeErrorAlert;
import com.google.appinventor.components.annotations.DesignerComponent;
import com.google.appinventor.components.annotations.DesignerProperty;
import com.google.appinventor.components.annotations.PropertyCategory;
import com.google.appinventor.components.annotations.SimpleEvent;
import com.google.appinventor.components.annotations.SimpleFunction;
import com.google.appinventor.components.annotations.SimpleObject;
import com.google.appinventor.components.annotations.SimpleProperty;
import com.google.appinventor.components.annotations.UsesLibraries;
import com.google.appinventor.components.annotations.UsesPermissions;
import com.google.appinventor.components.common.ComponentCategory;
import com.google.appinventor.components.common.PropertyTypeConstants;
import com.google.appinventor.components.runtime.util.AsynchUtil;
import com.google.appinventor.components.runtime.util.ErrorMessages;
import com.google.appinventor.components.runtime.util.YailList;
import com.google.appinventor.components.runtime.util.SdkLevel;

import com.google.appinventor.components.runtime.errors.YailRuntimeError;

import android.app.Activity;
import android.text.TextUtils;
import android.util.Log;
import android.os.StrictMode;

import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.net.InetSocketAddress;
import java.net.SocketException;

/**
* Simple Client Socket
* @author moicjarod@gmail.com (Jean-Rodolphe Letertre)
* with the help of the work of lizlooney @ google.com (Liz Looney) and
josmasflores @ gmail.com (Jose Dominguez)
* the help of Alexey Brylevskiy for debugging
* and the help of Hossein Amerkashi from AppyBuilder for compatibility with
AppyBuilder
*/
@DesignerComponent(version = 4,
description = "Non-visible component that provides client socket connectivity.",
category = ComponentCategory.EXTENSION,
nonVisible = true,
iconName = "http://jr.letertre.free.fr/Projets/AIClientSocket/clientsocket.png")
@SimpleObject(external = true)
@UsesPermissions(permissionNames = "android.permission.INTERNET")
public class ClientSocketAI2Ext extends AndroidNonvisibleComponent implements
Component
{
private static final String LOG_TAG = "ClientSocketAI2Ext";

private final Activity activity;

// the socket object


private Socket clientSocket = null;
// the address to connect to
private String serverAddress = "";
// the port to connect to
private String serverPort = "";
// connection timeout to server in ms
private int timeoutms = 2000;
// boolean that indicates the state of the connection, true = connected, false =
not connected
private boolean connectionState = false;
// boolean that indicates the mode used, false = string sent as is, true = String
is considered as hexadecimal data and will be converted before sending
// same behavior is used when receiving data
private boolean hexaStringMode = false;
// boolean to enable debug messages "YailRuntimeError" (true by default to ensure
same behavior as before)
private boolean debugMessages = true;

InputStream inputStream = null;

/**
* Creates a new Client Socket component.
*
* @param container the Form that this component is contained in.
*/
public ClientSocketAI2Ext(ComponentContainer container)
{
super(container.$form());
activity = container.$context();
// compatibility with AppyBuilder (thx Hossein Amerkashi <kkashi01 [at] gmail
[dot] com>)
StrictMode.ThreadPolicy policy = new
StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
}

/**
* Method that returns the server's address.
*/
@SimpleProperty(category = PropertyCategory.BEHAVIOR, description = "The address
of the server the client will connect to.")
public String ServerAddress()
{
return serverAddress;
}

/**
* Method to specify the server's address
*/
@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_STRING)
@SimpleProperty
public void ServerAddress(String address)
{
serverAddress = address;
}

/**
* Method that returns the server's port.
*/
@SimpleProperty(category = PropertyCategory.BEHAVIOR, description = "The port of
the server the client will connect to.")
public String ServerPort()
{
return serverPort;
}

/**
* Method to specify the server's port
*/
@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_STRING)
@SimpleProperty
public void ServerPort(String port)
{
serverPort = port;
}

/**
* Method that returns the timeout to server in ms
*/
@SimpleProperty(category = PropertyCategory.BEHAVIOR, description = "The timeout
to server in ms")
public int TimeoutMs()
{
return timeoutms;
}

/**
* Method to specify the timeout to server in ms
*/
@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_INTEGER,
defaultValue="2000")
@SimpleProperty
public void TimeoutMs(int timeout)
{
timeoutms = timeout;
}

/**
* Method that returns the connection state
*/
@SimpleProperty(category = PropertyCategory.BEHAVIOR, description = "The state of
the connection - true = connected, false = disconnected")
public boolean ConnectionState()
{
return connectionState;
}

/**
* Method that returns a string containing "\n\r" sequence
*/
@SimpleProperty(category = PropertyCategory.BEHAVIOR, description = "returns a
string containing \"\n\r\" sequence")
public String SeqNewLineAndRet()
{
String seq = "";
seq = seq + '\n' + '\r';
return seq;
}

/**
* Method that returns a string containing "\r\n" sequence
*/
@SimpleProperty(category = PropertyCategory.BEHAVIOR, description = "returns a
string containing \"\r\n\" sequence")
public String SeqRetAndNewLine()
{
String seq = "";
seq = seq + '\r' + '\n';
return seq;
}

/**
* Method that returns a string containing "\r" sequence
*/
@SimpleProperty(category = PropertyCategory.BEHAVIOR, description = "returns a
string containing \"\r\" sequence")
public String SeqRet()
{
String seq = "";
seq = seq + '\r';
return seq;
}

/**
* Method that returns a string containing "\n" sequence
*/
@SimpleProperty(category = PropertyCategory.BEHAVIOR, description = "returns a
string containing \"\n\" sequence")
public String SeqNewLine()
{
String seq = "";
seq = seq + '\n';
return seq;
}

/**
* Method that returns the mode (string or hexastring)
*/
@SimpleProperty(category = PropertyCategory.BEHAVIOR, description = "The mode of
sending and receiving data.")
public boolean HexaStringMode()
{
return hexaStringMode;
}

/**
* Method to specify the mode (string or hexastring)
*/
@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_BOOLEAN)
@SimpleProperty
public void HexaStringMode(boolean mode)
{
hexaStringMode = mode;
}

/**
* Method that returns the display of debug messages
*/
@SimpleProperty(category = PropertyCategory.BEHAVIOR, description = "The display
of debug messages.")
public boolean DebugMessages()
{
return debugMessages;
}

/**
* Method to specify the display of debug messages
*/
@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_BOOLEAN,
defaultValue="True")
@SimpleProperty
public void DebugMessages(boolean displayDebugMessages)
{
debugMessages = displayDebugMessages;
}

/**
* Creates the socket, connect to the server and launches the thread to receive
data from server
*/
@SimpleFunction(description = "Tries to connect to the server and launches the
thread for receiving data (blocking until connected or failed)")
public void Connect()
{
if (connectionState == true)
{
if (debugMessages == true)
throw new YailRuntimeError("Connect error, socket connected yet, please
disconnect before reconnect !", "Error");
}
try
{
// connecting the socket
clientSocket = new Socket();
clientSocket.connect(new InetSocketAddress(serverAddress,
Integer.parseInt(serverPort)), timeoutms);
connectionState = true;
// begin the receive loop in a new thread
AsynchUtil.runAsynchronously(new Runnable()
{
@Override
public void run()
{
ByteArrayOutputStream byteArrayOutputStream = new
ByteArrayOutputStream(1024);
byte[] buffer = new byte[1024];
int bytesRead = 0;
try
{
// get the input stream and save the data
inputStream = clientSocket.getInputStream();
while (true)
{
// test if there is a server problem then close socket properly (thx
Axeley :-))
try
{
bytesRead = inputStream.read(buffer);
if(bytesRead == -1)
break;
}
catch(SocketException e)
{
//throw e;
}
catch(IOException e)
{
if(e.getMessage().indexOf("ETIMEDOUT") >= 0)
break;
//throw e;
}

byteArrayOutputStream.write(buffer, 0, bytesRead);
final String dataReceived;
// hexaStringMode is false, so we don't transform the string received
if (hexaStringMode == false)
{
dataReceived = byteArrayOutputStream.toString("UTF-8");
}
// hexaStringMode is true, so we make a string with each character as
an hexa symbol representing the received message
else
{
int i;
char hexaSymbol1, hexaSymbol2;
String tempData = "";
byte[] byteArray = byteArrayOutputStream.toByteArray();
for (i = 0; i < byteArrayOutputStream.size(); i++)
{
if (((byteArray[i] & 0xF0) >> 4) < 0xA)
// 0 to 9 symbol
hexaSymbol1 = (char)(((byteArray[i] & 0xF0) >> 4) + 0x30);
else
// A to F symbol
hexaSymbol1 = (char)(((byteArray[i] & 0xF0) >> 4) + 0x37);
if ((byteArray[i] & 0x0F) < 0xA)
hexaSymbol2 = (char)((byteArray[i] & 0x0F) + 0x30);
else
hexaSymbol2 = (char)((byteArray[i] & 0x0F) + 0x37);
tempData = tempData + hexaSymbol1 + hexaSymbol2;
}

dataReceived = tempData;
}
// reset of the byteArrayOutputStream to flush the content
byteArrayOutputStream.reset();
// then we send the data to the user using an event
// events must be sent by the main thread (UI)
activity.runOnUiThread(new Runnable()
{
@Override
public void run()
{
DataReceived(dataReceived);
}
} );
}
// When we go there, either we have
// - server shutdown
// - disconnection asked (inputstream closed => -1 returned)
// - connection problem
// so, if it is not disconnected yet, we disconnect the socket and
inform the user of it.
if (connectionState == true)
{
Disconnect();
// events must be sent by the main thread (UI)
activity.runOnUiThread(new Runnable()
{
@Override
public void run()
{
RemoteConnectionClosed();
}
} );
}
}
catch (SocketException e)
{
Log.e(LOG_TAG, "ERROR_READ", e);
if (debugMessages == true)
throw new YailRuntimeError("Connect error (read 1) " +
e.getMessage(), "Error");
}
catch (IOException e)
{
Log.e(LOG_TAG, "ERROR_READ", e);
if (debugMessages == true)
throw new YailRuntimeError("Connect error (read 2)", "Error");
}
catch (Exception e)
{
connectionState = false;
Log.e(LOG_TAG, "ERROR_READ", e);
if (debugMessages == true)
throw new YailRuntimeError("Connect error (read 3) " +
e.getMessage(), "Error");
}
}
} );
}
catch (SocketException e)
{
Log.e(LOG_TAG, "ERROR_CONNECT", e);
if (debugMessages == true)
throw new YailRuntimeError("Connect error" + e.getMessage(), "Error");
}
catch (Exception e)
{
connectionState = false;
Log.e(LOG_TAG, "ERROR_CONNECT", e);
if (debugMessages == true)
throw new YailRuntimeError("Connect error (Socket Creation, please check Ip
or hostname -> )" + e.getMessage(), "Error");
}
}

/**
* Send data through the socket to the server
*/
@SimpleFunction(description = "Send data to the server")
public void SendData(final String data)
{
if (connectionState == false)
{
if (debugMessages == true)
throw new YailRuntimeError("Send error, socket not connected.", "Error");
}
final byte [] dataToSend;
byte [] dataCopy = data.getBytes();
if (hexaStringMode == false)
{
//dataSend = new byte [data.length()];
// if hexaStringMode is false, we send data as is
dataToSend = dataCopy;
}
else
{
// if hexaStringMode is true, we begin to verify we can transcode the symbols
// verify if the data we want to send contains only hexa symbols
int i;
for (i = 0; i < data.length(); i++)
{
if (((dataCopy[i] < 0x30) || (dataCopy[i] > 0x39)) && ((dataCopy[i] < 0x41)
|| (dataCopy[i] > 0x46)) && ((dataCopy[i] < 0x61) || (dataCopy[i] > 0x66)))
if (debugMessages == true)
throw new YailRuntimeError("Send data : hexaStringMode is selected and
non hexa symbol found in send String.", "Error");
}
// verify that the number of symbols is even
if ((data.length() %2) == 1)
{
if (debugMessages == true)
throw new YailRuntimeError("Send data : hexaStringMode is selected and
send String length is odd. Even number of characters needed.", "Error");
}
// if all tests pass, we transcode the data :
dataToSend=new byte[data.length()/2];
for (i = 0; i < data.length(); i=i+2)
{
byte [] temp1 = new byte [2];
temp1 [0] = dataCopy[i];
temp1 [1] = dataCopy[i+1];
String temp2 = new String (temp1);
dataToSend[i/2]=(byte)Integer.parseInt(temp2, 16);
}
// those two lines were removed because of a bug sending trailing 0x00 in
hexaString mode
// if 0x00 is really needed, we should add it directly in the string we
want to send
// end of c-type string character
// dataToSend[i/2] = (byte)0x00;
}

// we then send asynchonously the data


AsynchUtil.runAsynchronously(new Runnable()
{
@Override
public void run()
{
try
{
OutputStream out;
out = clientSocket.getOutputStream();
out.write(dataToSend);
}
catch (SocketException e)
{
Log.e(LOG_TAG, "ERROR_SEND", e);
if (debugMessages == true)
throw new YailRuntimeError("Send data" + e.getMessage(), "Error");
}
catch (Exception e)
{
Log.e(LOG_TAG, "ERROR_UNABLE_TO_SEND_DATA", e);
if (debugMessages == true)
throw new YailRuntimeError("Send Data", "Error");
}
}
} );
}

/**
* Close the socket
*/
@SimpleFunction(description = "Disconnect to the server")
public void Disconnect()
{
if (connectionState == true)
{
connectionState = false;
try
{
// shutdown the input socket,
//clientSocket.shutdownInput();
//clientSocket.shutdownOutput();
clientSocket.close();
}
catch (SocketException e)
{
// modifications by axeley too :-)
if(e.getMessage().indexOf("ENOTCONN") == -1)
{
Log.e(LOG_TAG, "ERROR_CONNECT", e);
if (debugMessages == true)
throw new YailRuntimeError("Disconnect" + e.getMessage(), "Error");
}
// if not connected, then just ignore the exception
}
catch (IOException e)
{
Log.e(LOG_TAG, "ERROR_CONNECT", e);
if (debugMessages == true)
throw new YailRuntimeError("Disconnect" + e.getMessage(), "Error");
}
catch (Exception e)
{
Log.e(LOG_TAG, "ERROR_CONNECT", e);
if (debugMessages == true)
throw new YailRuntimeError("Disconnect" + e.getMessage(), "Error");
}
finally
{
clientSocket=null;
}

}
else
if (debugMessages == true)
throw new YailRuntimeError("Socket not connected, can't disconnect.",
"Error");
}

/**
* Event indicating that a message has been received
*
* @param data the data sent by the server
*/
@SimpleEvent
public void DataReceived(String data)
{
// invoke the application's "DataReceived" event handler.
EventDispatcher.dispatchEvent(this, "DataReceived", data);
}

/**
* Event indicating that the remote socket closed the connection
*
*/
@SimpleEvent
public void RemoteConnectionClosed()
{
// invoke the application's "RemoteConnectionClosed" event handler.
EventDispatcher.dispatchEvent(this, "RemoteConnectionClosed");
}
}

You might also like