public class AndroidDeviceClient implements Serializable {

	private static final long serialVersionUID = -4688390733043132636L;

	/**
	 * The device serial number
	 */
	private final String serialNumber;

	/**
	 * The device model
	 */
	private final String model;

	/**
	 * The device client id
	 */
	private String client;

	private boolean online = true;

	public AndroidDeviceClient(String serialNumber, String model) {
		this.model = model;
		this.serialNumber = serialNumber;
	}

	/**
	 * Get device serial number
	 * 
	 * @return the device serial number
	 */
	public String getSerialNumber() {
		return serialNumber;
	}

	/**
	 * The device model
	 * 
	 * @return the device model (pretty string to show to users)
	 */
	public String getModel() {
		return model;
	}

	/**
	 * This device online state
	 * 
	 * @return true if the device is online, false otherwise
	 */
	public boolean isOnline() {
		return online;
	}

	/**
	 * set this device online state. For now it has the same online definition
	 * as ddms
	 * 
	 * @param online
	 *            true if online, false otherwise
	 */
	public void setOnline(boolean online) {
		this.online = online;
	}

	/**
	 * Set this device client address
	 * 
	 * @param client
	 *            the client id
	 */
	public void setClient(String client) {
		this.client = client;
	}

	/**
	 * Get this device client id
	 * 
	 * @return the client id
	 */
	public String getClient() {
		return client;
	}

	@Override
	public boolean equals(Object obj) {
		boolean equals = false;
		if (obj instanceof AndroidDeviceClient) {
			AndroidDeviceClient deviceClient = (AndroidDeviceClient) obj;
			if (deviceClient.getModel().equals(model)
					&& deviceClient.getSerialNumber().equals(serialNumber)) {
				equals = true;
			}

		}
		return equals;
	}

	@Override
	public String toString() {
		return model + "(" + serialNumber + ")";
	}

} public interface ClientChangedListener {

	/**
	 * Client just connected.
	 * 
	 * @param client
	 *            the client connected
	 */
	public void clientConnected(Client client);

	/**
	 * Client just disconnected.
	 * 
	 * @param client
	 *            the client connected
	 */
	public void clientDisconnected(Client client);

	/**
	 * Device data connection took place in the client and device described
	 * 
	 * @param client
	 *            the client connected
	 * @param device
	 *            the device serial number
	 */
	public void deviceConnected(Client client, String device);

	/**
	 * Device data connection stopped in the client and device described
	 * 
	 * @param client
	 *            the client connected
	 * @param device
	 *            the device serial number
	 */
	public void deviceDisconnected(Client client, String device);

	/**
	 * Device added in the client
	 * 
	 * @param client
	 *            the client connected
	 * @param device
	 *            the device serial number
	 */
	public void deviceAdded(Client client, String device);

	/**
	 * Device removed from client
	 * 
	 * @param client
	 *            the client connected
	 * @param device
	 *            the device serial number
	 */
	public void deviceRemoved(Client client, AndroidDeviceClient device);

} public class ClientLogger {
	private static final Logger logger = Logger
			.getLogger("RemoteResourcesClient"); //$NON-NLS-1$

	private static final String DEFAULT_CLIENT_LOGGER_DIR = RemoteResourcesConfiguration
			.getRemoteResourcesDir() != null ? RemoteResourcesConfiguration
			.getRemoteResourcesDir() + File.separator + "logs" : System //$NON-NLS-1$
			.getProperty("user.home");

	private static final String CLIENT_LOGGER_FILENAME = "remote_resources_client.log"; //$NON-NLS-1$

	private ClientLogger() {
	};

	static {
		logger.setLevel(Level.FINE);
		String outputDir = RemoteResourcesConfiguration.getInstance()
				.get(RemoteResourcesConfiguration.LOG_DIR,
						DEFAULT_CLIENT_LOGGER_DIR);
		File fOutputDir = new File(outputDir);

		if (!fOutputDir.exists()) {
			fOutputDir.mkdirs();
		}

		if (fOutputDir.exists()) {
			String outputFile = outputDir + File.separator
					+ CLIENT_LOGGER_FILENAME;

			try {
				Handler fileHandler = new FileHandler(outputFile);
				fileHandler.setFormatter(new SimpleFormatter());
				logger.addHandler(fileHandler);
			} catch (Exception e) {
				System.err
						.println(RemoteResourcesLocalization
								.getMessage(RemoteResourcesMessages.ClientLogger_Error_UnableToConfigure));
				e.printStackTrace(System.err);
			}
		} else {
			System.err
					.println(RemoteResourcesLocalization
							.getMessage(
									RemoteResourcesMessages.ClientLogger_Error_InsufficientPermissions,
									fOutputDir.getAbsolutePath()));
		}
	}

	public static void error(Class<?> clazz, String message, Throwable exception) {
		logger.logp(Level.SEVERE, clazz.getName(), null, message, exception);
	}

	public static void warning(Class<?> clazz, String message,
			Throwable exception) {
		logger.logp(Level.WARNING, clazz.getName(), null, message, exception);
	}

	public static void info(Class<?> clazz, String message, Throwable exception) {
		logger.logp(Level.INFO, clazz.getName(), null, message, exception);
	}

	public static void debug(Class<?> clazz, String message, Throwable exception) {
		logger.logp(Level.FINE, clazz.getName(), null, message, exception);
	}

} public class Client {

	private static final String DEFAULT_LOGCAT_PATH = RemoteResourcesConfiguration
			.getRemoteResourcesDir() != null ? RemoteResourcesConfiguration
			.getRemoteResourcesDir() + File.separator + "logs" : System
			.getProperty("user.home");

	private File logFilePath;

	private String logFileName;

	private Socket connection;

	private final InetAddress address;

	private final String hostname;

	private final int port;

	private ObjectOutputStream outputStream;

	private ObjectInputStream inputStream;

	private final Map<String, AndroidDeviceClient> devices = new HashMap<String, AndroidDeviceClient>();

	private final AtomicInteger sequenceNumber = new AtomicInteger();

	private final Map<Integer, Message> messages = new HashMap<Integer, Message>();

	private final Map<String, DataReceivedListener> dataListeners = new HashMap<String, DataReceivedListener>();

	private final Map<String, DataClient> dataConnections = new HashMap<String, DataClient>();

	private final ClientMessageHandler messageHandler;

	private final Status status = Status.OK;

	private DataReceivedListener sshotListener;

	private ClientDeviceChangesThread deviceChangesThread;

	private static final String LOG_FILE_EXTENSION = ".txt";

	public Client(String hostname, int port) throws UnknownHostException {
		InetAddress address = InetAddress.getByName(hostname);
		this.hostname = hostname;
		this.address = address;
		this.port = port != 0 ? port
				: IConnectionConstants.CONTROL_SERVER_DEFAULT_PORT;
		messageHandler = new ClientMessageHandler(this);
	}

	void connect() throws IOException {
		if (connection == null) {
			ClientLogger
					.info(Client.class,
							RemoteResourcesLocalization
									.getMessage(
											RemoteResourcesMessages.Client_Info_ConnectingToServer,
											new Object[] { hostname, port }),
							null);
			connection = new Socket(address, port);
			outputStream = new ObjectOutputStream(connection.getOutputStream());
			inputStream = new ObjectInputStream(connection.getInputStream());
			ClientInputHandler handler = new ClientInputHandler();
			handler.start();
			listDevices();
		}
	}

	void disconnect() {

		ClientLogger.info(Client.class, RemoteResourcesLocalization.getMessage(
				RemoteResourcesMessages.Client_Info_DisconnectingFromServer,
				new Object[] { hostname, port }), null);

		deviceChangesThread.cancel();
		for (String dataConnection : dataConnections.keySet()) {
			ClientConnectionManager.getInstance().stopDataConnection(this,
					dataConnection);
		}
		if (outputStream != null) {
			try {
				outputStream.close();
			} catch (IOException e) {
				// do nothing
			}
		}
		if (inputStream != null) {
			try {
				inputStream.close();
			} catch (IOException e) {
				// do nothing
			}
		}
		if (connection != null) {
			try {
				connection.close();
			} catch (IOException e) {
				// do nothing
			}
		}
		connection = null;
		outputStream = null;
		inputStream = null;
	}

	/**
	 * Return this client device list
	 * 
	 * @return a copy of the list of devices of this client
	 */
	public synchronized Map<String, AndroidDeviceClient> getDeviceList() {
		return devices;
	}

	/**
	 * Add a new device. Only intended to be used by message handler
	 * 
	 * @param device
	 *            the device
	 */
	void addDevice(AndroidDeviceClient device) {
		device.setClient(getDisplayName());
		if (devices.get(device.getSerialNumber()) == null) {
			devices.put(device.getSerialNumber(), device);
		}
	}

	void removeDevice(AndroidDeviceClient device) {
		if (isDataConnectionActive(device.getSerialNumber())) {
			ClientConnectionManager.getInstance().stopDataConnection(this,
					device.getSerialNumber());
		}
		devices.remove(device.getSerialNumber());

	}

	/**
	 * dispatch a get device list message to the server
	 */
	private void listDevices() {
		sendMessage(new GetDeviceListMessage(getNextSequenceNumber(),
				GetDeviceListMessageType.ALL));
	}

	/**
	 * Get a message from message pool. Only intended to be used by message
	 * handler;
	 * 
	 * @param sequenceNumber
	 *            the message sequence number
	 * @return the message with the desired sequence number or null with we have
	 *         no message
	 */
	Message getMessage(Integer sequenceNumber) {
		return messages.get(sequenceNumber);
	}

	/**
	 * Discard a message with a desired sequence number
	 * 
	 * @param sequenceNumber
	 *            the sequence number
	 */
	void discardMessage(Integer sequenceNumber) {
		messages.remove(sequenceNumber);
	}

	/**
	 * Send a message to the server associated with this client
	 * 
	 * @param message
	 *            the message
	 */
	public void sendMessage(Message message) {
		messages.put(message.getSequenceNumber(), message);
		try {
			outputStream.writeObject(message);
		} catch (IOException e) {
			ClientLogger
					.error(Client.class,
							RemoteResourcesLocalization
									.getMessage(
											RemoteResourcesMessages.Client_Error_UnableToSendMessage,
											new Object[] { hostname, port }), e);
			disconnect();
		} catch (NullPointerException e) {
			disconnect();
			try {
				connect();
				outputStream.writeObject(message);
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
		}

	}

	public int getNextSequenceNumber() {
		return sequenceNumber.getAndAdd(1);
	}

	/**
	 * Adds a new data received listener for a certain device
	 * 
	 * @param deviceSerialNumber
	 *            the device serial number
	 * @param dataReceivedListener
	 *            the listener
	 */
	public void addDataReceivedListener(String deviceSerialNumber,
			DataReceivedListener dataReceivedListener) {
		dataListeners.put(deviceSerialNumber, dataReceivedListener);
	}

	/**
	 * Remove a data received listener for a certain device
	 * 
	 * @param deviceSerialNumber
	 *            the device serial number
	 * @param dataReceivedListener
	 *            the listener to be removed
	 */
	public void removeDataReceivedListener(String deviceSerialNumber,
			DataReceivedListener dataReceivedListener) {
		dataListeners.remove(deviceSerialNumber);
	}

	/**
	 * Notify listeners about new data
	 * 
	 * @param serialNumber
	 *            the device serial number
	 * @param data
	 *            the received data
	 */
	void notifyDataReceived(String serialNumber, byte[] data) {
		DataReceivedListener listener = dataListeners.get(serialNumber);
		if (listener != null) {
			listener.newFrameReceived(serialNumber, data);
		}

	}

	/**
	 * Requests a new data connection with the device with the matching serial
	 * number. Does nothing if a connection is already done (in this case just
	 * register a new data listener)
	 * 
	 * Use the method {@link Client#isDataConnectionActive(String)} to check if
	 * the connection is already set
	 * 
	 * @param deviceSerialNumber
	 *            the device serial number
	 */
	void startDataConnection(String deviceSerialNumber) {
		if (!isDataConnectionActive(deviceSerialNumber)) {
			sendMessage(new SetupDeviceMessage(getNextSequenceNumber(),
					SetupDeviceMessageType.CONNECT_DEVICE, deviceSerialNumber,
					null));
		}
	}

	/**
	 * Stop device data connection.
	 * 
	 * @param deviceSerialNumber
	 *            the device serial number
	 */
	void stopDataConnection(String deviceSerialNumber) {
		if (isDataConnectionActive(deviceSerialNumber)) {
			dataConnections.remove(deviceSerialNumber).close();
			sendMessage(new SetupDeviceMessage(getNextSequenceNumber(),
					SetupDeviceMessageType.DISCONNECT_DEVICE,
					deviceSerialNumber, null));
		}
	}

	/**
	 * Connects the device data channel
	 * 
	 * @param deviceSerialNumber
	 *            the device serial number
	 * @param port
	 *            data connection port. The address will be the same as the
	 *            client
	 */
	void connectDeviceDataChannel(String deviceSerialNumber, Integer port) {
		DataClient dataClient = new DataClient(deviceSerialNumber, this,
				new InetSocketAddress(hostname, port));
		dataClient.start();
		dataConnections.put(deviceSerialNumber, dataClient);
	}

	/**
	 * Check if a data connection for a requested serial number is already in
	 * place
	 * 
	 * @param deviceSerialNumber
	 *            the device serial number
	 * @return true if data connection is active, false otherwise
	 */
	public boolean isDataConnectionActive(String deviceSerialNumber) {
		return dataConnections.get(deviceSerialNumber) != null;
	}

	/**
	 * Check if there is any data connection.
	 * 
	 * @return true if there is a data connection active, false otherwise
	 * */
	public boolean isAnyDataConnectionActive() {
		return !dataConnections.entrySet().isEmpty();
	}

	/**
	 * This class handles incoming messages from server side. This class is a
	 * thread so it could be ran in parallel.
	 * 
	 * @author Marcelo Marzola Bossoni
	 * 
	 */
	class ClientInputHandler extends Thread {

		@Override
		public void run() {
			// just read the next message in the stream and call the
			// appropriated handler
			try {
				Message message = (Message) inputStream.readObject();
				while (message != null) {
					messageHandler.handleMessage(message);
					message = (Message) inputStream.readObject();
				}
			} catch (ClassNotFoundException e) {
				ClientLogger
						.error(ClientInputHandler.class,
								RemoteResourcesLocalization
										.getMessage(
												RemoteResourcesMessages.Client_Error_UnknownObjectType,
												new Object[] { hostname, port }),
								e);
				disconnect();
			} catch (IOException e) {
				ClientLogger.error(ClientInputHandler.class,
						RemoteResourcesLocalization.getMessage(
								RemoteResourcesMessages.Client_Error_IOError,
								new Object[] { hostname, port }), e);
				disconnect();
			}
		}

	}

	class ClientDeviceChangesThread extends Thread {
		private boolean cancel = false;

		@Override
		public void run() {
			while (!cancel) {
				try {
					Thread.sleep(5000);
				} catch (InterruptedException e) {
					// do nothing
				}
				sendMessage(new GetDeviceListMessage(getNextSequenceNumber(),
						GetDeviceListMessageType.REMOVED));
				sendMessage(new GetDeviceListMessage(getNextSequenceNumber(),
						GetDeviceListMessageType.ADDED));
			}
		}

		public synchronized void cancel() {
			cancel = true;
		}

	}

	public synchronized boolean isConnected() {
		return deviceChangesThread != null;
	}

	synchronized void setConnected() {
		if (deviceChangesThread == null) {
			deviceChangesThread = new ClientDeviceChangesThread();
			deviceChangesThread.start();
		}
	}

	public int getPort() {
		return port;
	}

	public String getHostname() {
		return hostname;
	}

	/**
	 * Get a textual representation of this client name
	 * 
	 * @return the string representing this client name
	 */
	public String getDisplayName() {
		return hostname + ":" + port; //$NON-NLS-1$
	}

	/**
	 * Return this client status. Errors are expected on client connection
	 * errors
	 * 
	 * @return the actual client status
	 */
	public Status getStatus() {
		return status;
	}

	/**
	 * Set the time between two screen captures
	 * 
	 * @param serialNumber
	 *            the device serial number
	 * @param value
	 *            the time (in ms) to wait before the next screen capture
	 */
	public void setPollingRate(String serialNumber, String value) {
		if (isDataConnectionActive(serialNumber)) {
			try {
				sendMessage(new SetupDeviceMessage(getNextSequenceNumber(),
						SetupDeviceMessageType.SET_POLLING_RATE, serialNumber,
						Long.parseLong(value)));
			} catch (NumberFormatException e) {
				ClientLogger.warning(Client.class,
						"Unable to set polling rate for device", e);
			}
		}
	}

	/**
	 * Set the image quality
	 * 
	 * @param serialNumber
	 *            the device serial number
	 * @param value
	 *            the value, between 0 and 100, of image quality
	 */
	public void setImageQuality(String serialNumber, String value) {
		if (isDataConnectionActive(serialNumber)) {
			try {
				sendMessage(new SetupDeviceMessage(getNextSequenceNumber(),
						SetupDeviceMessageType.SET_IMAGE_QUALITY, serialNumber,
						Integer.parseInt(value)));
			} catch (NumberFormatException e) {
				ClientLogger.warning(Client.class,
						"Unable to set image quality for device", e);
			}
		}
	}

	public void takeScreenshot(String serialNumber,
			DataReceivedListener listener) {
		sshotListener = listener;
		sendMessage(new GetNewFrameMessage(getNextSequenceNumber(),
				serialNumber, 75));
	}

	void saveScreenshot(String serialNumber, byte[] sshot) {
		sshotListener.newFrameReceived(serialNumber, sshot);
		sshotListener = null;
	}

	public String generateNameFileLog(String device) {

		String date_mask = "yyyyMMdd";

		String device_nickname = "LogFile_";
		if (DeviceUtils.getInstance().getDeviceNickname(device) != null) {
			device_nickname += DeviceUtils.getInstance().getDeviceNickname(
					device)
					+ "_";
		}

		java.util.Date today = new java.util.Date();
		Calendar cal = Calendar.getInstance();
		cal.setTime(today);
		SimpleDateFormat format_date = new SimpleDateFormat(date_mask);

		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.append(device_nickname);
		stringBuilder.append(getHostname());
		stringBuilder.append("_");
		stringBuilder.append(String.valueOf(getPort()));
		stringBuilder.append("_");
		stringBuilder.append(format_date.format(today));
		stringBuilder.append("_");
		stringBuilder.append(cal.get(Calendar.HOUR_OF_DAY));
		stringBuilder.append(cal.get(Calendar.MINUTE));
		stringBuilder.append(cal.get(Calendar.SECOND));
		String logfile_name = stringBuilder.toString();

		return logfile_name + LOG_FILE_EXTENSION;
	}

	public String generateNameFileLog() {
		return generateNameFileLog("");
	}

	public void createFileLog(String deviceSerialNumber, String data) {

		try {
			String outputFile;
			if (logFilePath == null) {
				outputFile = DEFAULT_LOGCAT_PATH + File.separator + logFileName;
			} else {
				outputFile = logFilePath.getAbsolutePath() + File.separator
						+ logFileName;
			}

			FileWriter fw = new FileWriter(outputFile, true);
			fw.write(data);
			fw.close();
		} catch (IOException e) {
			// TODO: log exception
		}

	}

	public String getDefaultLogcatPath() {
		return DEFAULT_LOGCAT_PATH;
	}

	/**
	 * Handle the logcat Capture
	 * 
	 * @param serialNumber
	 *            the device serial number
	 * @param file
	 *            complete name of the log to be saved
	 */
	public void getLogCatCapture(String deviceSerialNumber, File file,
			String name, ControlLogCaptureTypeMessage level) {
		if (isDataConnectionActive(deviceSerialNumber)) {
			logFilePath = file;
			logFileName = name;
			sendMessage(new ControlLogCaptureMessage(getNextSequenceNumber(),
					deviceSerialNumber, level));
		}
	}

	/*
	 * public void handleFile(String deviceSerialNumber, String localFile,
	 * String remoteFile, ControlFileHandlingTypeMessage handlingType) { if
	 * (isDataConnectionActive(deviceSerialNumber)) { sendMessage(new
	 * ControlFileHandlingMessage(getNextSequenceNumber(), deviceSerialNumber,
	 * localFile, remoteFile, handlingType)); } }
	 * 
	 * public void runShellCommand(String deviceSerialNumber, String command) {
	 * if (isDataConnectionActive(deviceSerialNumber)) { sendMessage(new
	 * ControlRunShellCommandMessage( getNextSequenceNumber(),
	 * deviceSerialNumber, command)); } }
	 */

	public void runCommand(String deviceSerialNumber, Command command) {
		if (isDataConnectionActive(deviceSerialNumber)) {
			Message m = CommandMessageFactory.getCommandMessage(command,
					getNextSequenceNumber(), deviceSerialNumber);
			sendMessage(m);
		}
	}

	// TODO Handle the adb shell response. Needs some UI to display the results
	public void displayRunShellCommandResult(String serialNumber, String data) {
		ClientLogger.debug(ControlRunShellCommandMessage.class, data, null);

		JOptionPane.showMessageDialog(null, data, "ADB command result",
				JOptionPane.ERROR_MESSAGE);
	}

	// TODO Handle the file handling response. Needs some UI to display the
	// results
	public void displayFileHandlingResult(String serialNumber, String data) {
		ClientLogger.debug(ControlRunShellCommandMessage.class, data, null);

		JOptionPane.showMessageDialog(null, data, "File handling result",
				JOptionPane.ERROR_MESSAGE);
	}

	// TODO Handle the package handling response. Needs some UI to display the
	// results
	public void displayPackageHandlingResult(String serialNumber, String data) {
		ClientLogger.debug(ControlPackageHandlingMessage.class, data, null);

		JOptionPane.showMessageDialog(null, data, "Package handling result",
				JOptionPane.ERROR_MESSAGE);
	}

	// TODO Handle the reboot response. Needs some UI to display the
	// results
	public void displayRebootCommandResult(String serialNumber, String data) {
		ClientLogger.debug(ControlPackageHandlingMessage.class, data, null);

		JOptionPane.showMessageDialog(null, data, "Reboot result",
				JOptionPane.ERROR_MESSAGE);
	}
} public class ClientConnectionManager {

	public static final String LOCALHOST = IConnectionConstants.LOCALHOST;

	public static final int LOCALPORT = RemoteResourcesConfiguration
			.getInstance().getServerPort();

	private static ClientConnectionManager instance;

	private final Map<String, Client> clients = new HashMap<String, Client>();

	private final Set<ClientChangedListener> listeners;

	/**
	 * We currently support only one active device connected
	 */
	private Client activeClient = null;

	private String activeDevice = null;

	private ClientConnectionManager() {
		listeners = new HashSet<ClientChangedListener>();
	}

	/**
	 * Add a client changed listener. Has no effect if listener is already
	 * registered
	 * 
	 * @param listener
	 *            the listener
	 */
	public void addClientChangedListener(ClientChangedListener listener) {
		listeners.add(listener);
	}

	/**
	 * Remove a client changed listener
	 * 
	 * @param listener
	 *            the listener
	 */
	public void removeClientChangedListener(ClientChangedListener listener) {
		listeners.remove(listener);
	}

	public static ClientConnectionManager getInstance() {
		if (instance == null) {
			instance = new ClientConnectionManager();
		}
		return instance;
	}

	/**
	 * Connect to a server
	 * 
	 * @param hostName
	 *            the address to connect to
	 * @param port
	 *            the port
	 * @return the client connection
	 * @throws IOException
	 *             if no connection was possible
	 * @throws UnknownHostException
	 *             if was not possible to resolve host
	 */
	public Client connect(String hostName, int port)
			throws UnknownHostException, IOException {
		final Client client = new Client(hostName, port);
		client.connect();
		new Thread("Check device state") { //$NON-NLS-1$
			@Override
			public void run() {
				while (!client.isConnected()
						&& (client.getStatus() == Status.OK)) {
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						// do nothing
					}
				}
				if (client.getStatus() == Status.OK) {
					List<ClientChangedListener> copy = new ArrayList<ClientChangedListener>();
					copy.addAll(listeners);
					for (ClientChangedListener listener : copy) {
						listener.clientConnected(client);
					}
				}
			};
		}.start();
		clients.put(hostName + ":" + port, client); //$NON-NLS-1$
		return client;
	}

	private void removeClient(String hostname, int port) {
		clients.remove(hostname + ":" + port); //$NON-NLS-1$
	}

	public void disconnect(String hostname, int port) {
		Client client = getClient(hostname, port);
		if (client != null) {
			removeClient(hostname, port);
			disconnect(client);
		}
		List<ClientChangedListener> copy = new ArrayList<ClientChangedListener>();
		copy.addAll(listeners);
		for (ClientChangedListener listener : copy) {
			listener.clientDisconnected(client);
		}
	}

	public void disconnect(Client client) {
		client.disconnect();
	}

	public Client getClient(String hostname, int port) {
		return clients.get(hostname + ":" + port); //$NON-NLS-1$
	}

	public Client getClient(String id) {
		return clients.get(id);
	}

	public void addDevice(Client c, AndroidDeviceClient device) {
		if (c.getDeviceList().get(device.getSerialNumber()) == null) {
			c.addDevice(device);
			List<ClientChangedListener> copy = new ArrayList<ClientChangedListener>();
			copy.addAll(listeners);
			for (ClientChangedListener listener : copy) {
				listener.deviceAdded(c, device.getSerialNumber());
			}
		}
	}

	public void removeDevice(Client c, AndroidDeviceClient device) {
		c.removeDevice(device);
		List<ClientChangedListener> copy = new ArrayList<ClientChangedListener>();
		copy.addAll(listeners);
		for (ClientChangedListener listener : copy) {
			listener.deviceRemoved(c, device);
		}
	}

	public void startDataConnection(Client c, String device) {
		if ((activeClient != null) && (activeDevice != null)) {
			stopDataConnection(activeClient, activeDevice);
		}
		c.startDataConnection(device);
		activeClient = c;
		activeDevice = device;
		List<ClientChangedListener> copy = new ArrayList<ClientChangedListener>();
		copy.addAll(listeners);
		for (ClientChangedListener listener : copy) {
			listener.deviceConnected(c, device);
		}
	}

	public void stopDataConnection(Client c, String device) {
		if (c.isDataConnectionActive(device)) {
			c.stopDataConnection(device);
			List<ClientChangedListener> copy = new ArrayList<ClientChangedListener>();
			copy.addAll(listeners);
			for (ClientChangedListener listener : copy) {
				listener.deviceDisconnected(c, device);
			}
		}
	}

	/**
	 * Close this manager
	 */
	public void close() {
		// do nothing
	}

} public interface DataReceivedListener {

	/**
	 * Inform about a new data frame received
	 * 
	 * @param serialNumber
	 *            the device serialNumber
	 * @param frameContent
	 *            the content
	 */
	public void newFrameReceived(String serialNumber, byte[] frameContent);

} public class EventsListener extends MouseAdapter {

	private final AndroidDeviceClient device;

	private final Client client;

	public EventsListener(Client client, String device) {
		this.device = client.getDeviceList().get(device);
		this.client = client;
	}

	@Override
	public void mouseDragged(MouseEvent e) {
		processEvent(e, TouchCommandMessageType.TOUCH_MOVE);
	}

	@Override
	public void mousePressed(MouseEvent e) {
		processEvent(e, TouchCommandMessageType.TOUCH_DOWN);
	}

	@Override
	public void mouseReleased(MouseEvent e) {
		processEvent(e, TouchCommandMessageType.TOUCH_UP);
	}

	private void processEvent(MouseEvent e, TouchCommandMessageType down) {
		Dimension componentDimension = e.getComponent().getSize();

		float xRate = (float) e.getX() / (float) componentDimension.width;
		float yRate = (float) e.getY() / (float) componentDimension.height;

		switch (down) {
		case TOUCH_DOWN:
			client.sendMessage(new TouchCommandMessage(client
					.getNextSequenceNumber(), device.getSerialNumber(),
					TouchCommandMessageType.TOUCH_DOWN, xRate, yRate));
			Script.registerActionTouch(TouchCommandMessageType.TOUCH_DOWN,
					String.valueOf(xRate), String.valueOf(yRate));
			break;
		case TOUCH_MOVE:
			client.sendMessage(new TouchCommandMessage(client
					.getNextSequenceNumber(), device.getSerialNumber(),
					TouchCommandMessageType.TOUCH_MOVE, xRate, yRate));
			Script.registerActionTouch(TouchCommandMessageType.TOUCH_MOVE,
					String.valueOf(xRate), String.valueOf(yRate));
			break;
		case TOUCH_UP:
			client.sendMessage(new TouchCommandMessage(client
					.getNextSequenceNumber(), device.getSerialNumber(),
					TouchCommandMessageType.TOUCH_UP, xRate, yRate));
			Script.registerActionTouch(TouchCommandMessageType.TOUCH_UP,
					String.valueOf(xRate), String.valueOf(yRate));
			break;
		}
	}
} public class ScreenPanel extends Canvas {

	private static final long serialVersionUID = 3614027524294740275L;

	private boolean isAnimatedGif = false;

	public ScreenPanel() {
		setIgnoreRepaint(true);
	}

	/**
	 * 
	 * @param image
	 */
	public void drawScreen(Image image, boolean isAnimatedGif) {
		this.isAnimatedGif = isAnimatedGif;
		drawScreen(image);
	}

	public synchronized void stopAnimation() {
		isAnimatedGif = false;
	}

	public void drawScreen(Image image) {
		Dimension parentSize = getParent().getSize();
		Image imageToPaint = image;

		BufferStrategy strategy = getBufferStrategy();
		Graphics2D g = (Graphics2D) strategy.getDrawGraphics();

		if (image != null) {
			if (image instanceof BufferedImage) {
				float resizeFactor = ImageUtils.calculateResizeFactor(
						(BufferedImage) image, parentSize);
				imageToPaint = ImageUtils.resizeImage((BufferedImage) image,
						resizeFactor);
			}

			int xLocation = (getParent().getSize().width / 2)
					- (imageToPaint.getWidth(null) / 2);
			int yLocation = (getParent().getSize().height / 2)
					- (imageToPaint.getHeight(null) / 2);

			this.setSize(new Dimension(imageToPaint.getWidth(null),
					imageToPaint.getHeight(null)));

			this.setLocation(xLocation, yLocation);

			g.drawImage(imageToPaint, 0, 0, this);
		} else {
			g.setColor(getParent().getBackground());
			g.fillRect(0, 0, parentSize.width, parentSize.height);
		}

		g.dispose();
		strategy.show();
	}

	@Override
	public boolean imageUpdate(Image img, int infoflags, int x, int y, int w,
			int h) {
		if (isAnimatedGif) {
			drawScreen(img);
		}
		return isAnimatedGif;
	}
} public class Device {

	public static final String HEADER = "[device]";

	public static final String NAME = "name";

	public static final String SERIAL = "sn";

	public static final String HOST = "host";

	public static final String POLLING_RATE = "polling";

	public static final String IMAGE_QUALITY = "quality";

	public static final String SCRIPT_GENERATION = "script";

	public static final String SCRIPT_PATH = "scriptPath";

	public static final String SCRIPT_NAME = "scriptName";

	private final Map<String, String> deviceProperties = new HashMap<String, String>();

	public Device(String name, String host, String serialNumber) {
		deviceProperties.put(NAME, name);
		deviceProperties.put(HOST, host);
		deviceProperties.put(SERIAL, serialNumber);
		deviceProperties.put(SCRIPT_GENERATION, "false");
		deviceProperties
				.put(SCRIPT_PATH,
						RemoteResourcesConfiguration.getRemoteResourcesDir() != null ? RemoteResourcesConfiguration
								.getRemoteResourcesDir()
								+ File.separator
								+ "scripts" : System //$NON-NLS-1$
								.getProperty("user.home"));
		deviceProperties.put(SCRIPT_NAME, generateNameScriptFile(name, host));
		deviceProperties.put(POLLING_RATE,
				Long.toString(IConnectionConstants.DEFAULT_FRAME_DELAY));
		deviceProperties.put(IMAGE_QUALITY,
				Integer.toString(IConnectionConstants.DEFAULT_IMAGE_QUALITY));
	}

	private String generateNameScriptFile(String name, String host) {

		String date_mask = "yyyyMMdd";

		String device_nickname = "ScriptFile_";
		if (name != null) {
			device_nickname += name + "_";
		}

		java.util.Date today = new java.util.Date();
		Calendar cal = Calendar.getInstance();
		cal.setTime(today);
		SimpleDateFormat format_date = new SimpleDateFormat(date_mask);

		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.append(device_nickname);
		stringBuilder.append("_");
		stringBuilder.append(host);
		stringBuilder.append("_");
		stringBuilder.append(format_date.format(today));
		String script_name = stringBuilder.toString() + ".txt";

		return script_name;
	}

	public void setHost(String host) {
		deviceProperties.put(HOST, host);
	}

	public String getHost() {
		return deviceProperties.get(HOST);
	}

	public void setSerialNumber(String sn) {
		deviceProperties.put(SERIAL, sn);
	}

	public String getSerialNumber() {
		return deviceProperties.get(SERIAL);
	}

	public String getName() {
		return deviceProperties.get(NAME);
	}

	public void setName(String name) {
		deviceProperties.put(NAME, name);
	}

	public String getProperty(String property) {
		return deviceProperties.get(property);
	}

	public void setProperty(String property, String value) {
		deviceProperties.put(property, value);
	}

	public Map<String, String> getProperties() {
		return deviceProperties;
	}

	public boolean getScriptGeneration() {
		if (deviceProperties.get(SCRIPT_GENERATION).equals("true")) {
			return true;
		} else {
			return false;
		}
	}

	public void setScriptGeneration(boolean b) {
		if (b == true) {
			deviceProperties.put(SCRIPT_GENERATION, "true");
		} else {
			deviceProperties.put(SCRIPT_GENERATION, "false");
		}
	}

	public void write(FileOutputStream outputStream) throws IOException {
		StringBuilder builder = new StringBuilder();
		builder.append(HEADER);
		builder.append("\n");

		for (String key : deviceProperties.keySet()) {
			builder.append(key);
			builder.append("=");
			builder.append(deviceProperties.get(key));
			builder.append("\n");
		}

		outputStream.write(builder.toString().getBytes());
	}

	public static Device read(BufferedReader reader) throws IOException {

		boolean finished = false;
		String line;
		Map<String, String> properties = new HashMap<String, String>();

		reader.mark(1024);
		line = reader.readLine();
		if ((line != null) && line.startsWith("[")) {
			reader.reset();
			finished = true;
		}
		while ((line != null) && !finished) {
			properties.put(line.split("=")[0], line.split("=")[1]);
			reader.mark(1024);
			line = reader.readLine();
			if ((line != null) && line.startsWith("[")) {
				reader.reset();
				finished = true;
			}
		}
		Device dev = new Device(null, null, null);
		for (String prop : properties.keySet()) {
			dev.setProperty(prop, properties.get(prop));
		}
		return dev;
	}

	@Override
	public boolean equals(Object obj) {
		boolean equal = false;
		if (obj instanceof Device) {
			Device dev = (Device) obj;
			Map<String, String> otherDevProps = dev.getProperties();
			boolean areAllEqual = true;
			if (otherDevProps.size() == deviceProperties.size()) {
				for (String prop : deviceProperties.keySet()) {
					areAllEqual = areAllEqual
							&& deviceProperties.get(prop).equals(
									otherDevProps.get(prop));
				}
				equal = areAllEqual;
			}
		}
		return equal;
	}

	// copies the properties of this device into the argument
	public void copyProperties(Device d) {
		setHost(d.getHost());
		setName(d.getName());
		setProperty(POLLING_RATE, d.getProperty(POLLING_RATE));
		setProperty(IMAGE_QUALITY, d.getProperty(IMAGE_QUALITY));
		setProperty(SCRIPT_GENERATION, d.getProperty(SCRIPT_GENERATION));
		setProperty(SCRIPT_PATH, d.getProperty(SCRIPT_PATH));
		setProperty(SCRIPT_NAME, d.getProperty(SCRIPT_NAME));
	}
} public class ImageUtils {

	/**
	 * 
	 * @param image
	 * @param scale
	 * @return
	 */
	public static BufferedImage resizeImage(BufferedImage image, double scale) {
		if ((scale == 1.0) || (image == null)) {
			return image;
		}

		int type = image.getType() == 0 ? BufferedImage.TYPE_INT_ARGB : image
				.getType();

		float width = image.getWidth();
		float height = image.getHeight();

		int newHeight = (int) Math.round(height * scale);
		int newWidth = Math.round((width * newHeight) / height);

		BufferedImage resizedImage = new BufferedImage(newWidth, newHeight,
				type);
		Graphics2D g = resizedImage.createGraphics();

		g.setComposite(AlphaComposite.Src);

		g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
				RenderingHints.VALUE_INTERPOLATION_BILINEAR);
		g.setRenderingHint(RenderingHints.KEY_RENDERING,
				RenderingHints.VALUE_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);

		g.drawImage(image, 0, 0, newWidth, newHeight, null);
		g.dispose();

		return resizedImage;
	}

	public static float calculateResizeFactor(BufferedImage image,
			Dimension sizeToFit, int border) {
		if (image == null) {
			return 1;
		}

		double height = sizeToFit.getHeight() - (2 * border);
		double scaleHeight = (float) (height / image.getHeight());

		double width = sizeToFit.getWidth() - (2 * border);
		double scaleWidth = (float) (width / image.getWidth());

		float scale = (float) (scaleHeight <= scaleWidth ? scaleHeight
				: scaleWidth);

		return scale;
	}

	public static float calculateResizeFactor(BufferedImage image,
			Dimension sizeToFit) {
		return calculateResizeFactor(image, sizeToFit, 10);
	}

	/**
	 * <b>Compression temporarily disabled due to problems on full hd
	 * devices.</b> Compress a BufferedImage from chimpchat into a compressed Xz
	 * file with a Jpeg image within
	 * 
	 * @param sourceImage
	 *            the source image
	 * @param quality
	 *            the quality rate: 100 for max QUALITY, 0 for max COMPRESSION
	 * @return a byte array representing the image
	 * @throws IOException
	 *             if some error occurs during the conversion
	 */

	public static ByteArrayOutputStream compress(BufferedImage sourceImage,
			int quality) throws IOException {

		ByteArrayOutputStream imageOutput = new ByteArrayOutputStream();
		ByteArrayOutputStream xzOutput = new ByteArrayOutputStream();
		ImageWriter imgWriter = ImageIO.getImageWritersByFormatName("jpeg")
				.next();
		ImageWriteParam parameters = imgWriter.getDefaultWriteParam();
		parameters.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
		parameters.setCompressionQuality((quality > 100 ? 100
				: (quality < 0 ? 0 : quality)) / 100f);
		ImageOutputStream imgOutputStream = new MemoryCacheImageOutputStream(
				imageOutput);
		BufferedImage newImage = new BufferedImage(sourceImage.getWidth(),
				sourceImage.getHeight(), BufferedImage.TYPE_INT_RGB);
		ColorConvertOp colorConvertion = new ColorConvertOp(null);
		colorConvertion.filter(sourceImage, newImage);
		IIOImage image = new IIOImage(newImage, null, null);
		imgWriter.setOutput(imgOutputStream);
		imgWriter.write(null, image, parameters);
		/*
		 * XZOutputStream xzStream = new XZOutputStream(xzOutput, new
		 * LZMA2Options(LZMA2Options.PRESET_MAX));
		 */
		xzOutput.write(imageOutput.toByteArray());
		xzOutput.close();
		return xzOutput;
	}

	/**
	 * <b>Compression temporarily disabled due to problems on full hd
	 * devices.</b> Decompress a previously compressed Xz stream
	 * 
	 * @param xzStream
	 *            the compressed file
	 * @return the original file (notice that if file was compressed using JPEG,
	 *         you will not be able to retrieve the original file)
	 * @throws IOException
	 *             if any error occurs during decompression
	 */

	public static ByteArrayInputStream decompress(ByteArrayInputStream xzStream)
			throws IOException {
		ByteArrayInputStream xzInputStream = xzStream;
		byte firstByte = (byte) xzInputStream.read();
		byte[] buffer = new byte[xzInputStream.available()];
		buffer[0] = firstByte;
		xzInputStream.read(buffer, 1, buffer.length - 2);
		xzInputStream.close();
		return new ByteArrayInputStream(buffer);
	}

	public static boolean isEqual(BufferedImage newImage,
			BufferedImage previousImage) {
		boolean equal = true;

		if ((newImage != null) && (previousImage != null)) {
			DataBuffer newDb = newImage.getData().getDataBuffer();
			DataBuffer prevDb = previousImage.getData().getDataBuffer();

			for (int i = 0; (i < newDb.getSize()) && equal; i++) {
				if (newDb.getElem(i) != prevDb.getElem(i)) {
					return false;
				}
			}

		} else {
			equal = false;
		}

		return equal;
	}
} public class ResourcesLoader {
	public static final String RESOURCES_PATH = "com/eldorado/remoteresources/resources/";

	private static final Map<String, Image> cachedImages = new HashMap<String, Image>();

	private static final Map<String, Icon> cachedIcons = new HashMap<String, Icon>();

	/**
	 * 
	 * @param resourceName
	 * @return
	 */
	public static Image getImageResource(String resourceName, String extension,
			int size) {
		Image image = cachedImages.get(getImageKey(resourceName, size));

		if (image == null) {
			try {
				InputStream imageInputStream = loadResourceInputStream("images/"
						+ resourceName + extension);
				image = ImageIO.read(imageInputStream);
				if (size > 0) {
					if (image.getWidth(null) > image.getHeight(null)) {
						image = image.getScaledInstance(size, -1,
								Image.SCALE_SMOOTH);
					} else {
						image = image.getScaledInstance(-1, size,
								Image.SCALE_SMOOTH);
					}
				}
				cachedImages.put(getImageKey(resourceName, size), image);
			} catch (IOException e) {
				// TODO: log the information
			}
		}
		return image;
	}

	public static Image getImageResource(String resourceName, int size) {
		return getImageResource(resourceName, ".png", size);

	}

	public static Icon getIcon(String resourceName, int size) {
		return getIcon(resourceName, ".png", size);
	}

	public static Icon getIcon(String resourceName, String extension, int size) {
		Icon icon = cachedIcons.get(getImageKey(resourceName, size));
		if (icon == null) {
			icon = new ImageIcon(
					getImageResource(resourceName, extension, size));
			cachedIcons.put(getImageKey(resourceName, size), icon);
		}
		return icon;
	}

	public static Image getAnimatedGif(String resourceName) {
		URL url = ClassLoader.getSystemClassLoader().getResource(
				RESOURCES_PATH + "images/" + resourceName + ".gif");
		ImageIcon icon = new ImageIcon(url);
		return icon.getImage();
	}

	private static String getImageKey(String resourceName, int size) {
		return resourceName + "_" + size;
	}

	/**
	 * 
	 * @param resourceName
	 * @return
	 */
	public static Font getFontResource(String resourceName) {
		Font font;

		try {
			InputStream fontInputStream = loadResourceInputStream("fonts/"
					+ resourceName + ".ttf");
			font = Font.createFont(Font.PLAIN, fontInputStream);
		} catch (FontFormatException | IOException | NullPointerException e) {
			font = null;
		}

		return font;

	}

	/**
	 * 
	 * @param resourceName
	 * @return
	 */
	public static StringBuilder getFileResourceContent(String resourceName) {
		StringBuilder sb = new StringBuilder();
		BufferedReader br = new BufferedReader(new InputStreamReader(
				loadResourceInputStream("files/" + resourceName)));
		try {
			String line;
			for (; (line = br.readLine()) != null;) {
				sb.append(line + "\n");
			}
		} catch (IOException e) {
			return null;
		} finally {
			try {
				br.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

		return sb;

	}

	private static InputStream loadResourceInputStream(String resourceName) {
		ClassLoader classLoader = ClassLoader.getSystemClassLoader();

		InputStream resourceStream = classLoader
				.getResourceAsStream(RESOURCES_PATH + resourceName);

		return resourceStream;
	}
}
import java.awt.Dimension;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import javax.imageio.ImageIO;
import javax.swing.JOptionPane;
import com.eldorado.remoteresources.android.client.AndroidDeviceClient;
import com.eldorado.remoteresources.android.client.ClientChangedListener;
import com.eldorado.remoteresources.android.client.ClientLogger;
import com.eldorado.remoteresources.android.client.connection.Client;
import com.eldorado.remoteresources.android.client.connection.ClientConnectionManager;
import com.eldorado.remoteresources.android.client.connection.DataReceivedListener;
import com.eldorado.remoteresources.ui.EventsListener;
import com.eldorado.remoteresources.ui.ScreenPanel;
import com.eldorado.remoteresources.ui.model.Device;
import com.eldorado.remoteresources.utils.ImageUtils;
import com.eldorado.remoteresources.utils.ResourcesLoader;
/*
 * (C) Copyright 2014 Instituto de Pesquisas Eldorado (http://www.eldorado.org.br/).
 *
 * This file is part of the software Remote Resources
 *
 * All rights reserved. This file and the accompanying materials
 * are made available under the terms of the GNU Lesser General Public License
 * (LGPL) version 3.0 which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl.html
 *
 * This file is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 */

package com.eldorado.remoteresources.android.client.screencapture;




/**
 * 
 *
 * 
 */
public class CaptureManager implements ClientChangedListener {

	private static CaptureManager instance;

	private final ScreenPanel canvas;

	private Client client;