/** ** Copyright (C) SAS Institute, All rights reserved. ** General Public License: http://www.opensource.org/licenses/gpl-license.php **/ package com.jayway.android.robotium.remotecontrol.solo; import java.util.Properties; import java.util.Vector; import java.util.concurrent.TimeoutException; import org.safs.android.auto.lib.AndroidTools; import org.safs.android.auto.lib.DUtilities; import org.safs.sockets.ConnectionListener; import org.safs.sockets.DebugListener; import org.safs.sockets.NamedListener; import org.safs.sockets.RemoteException; import org.safs.sockets.ShutdownInvocationException; import org.safs.sockets.SocketProtocol; import org.safs.sockets.SocketProtocolListener; import org.safs.sockets.android.DroidSocketProtocol; /** * * @author Carl Nagle, SAS Institute, Inc * * OCT 11, 2012 (SBJLWA) Remove field startEmulatorPortForwarding, because we can use portForwarding instead. * FEB 06, 2013 (CANAGL) Fixed performRemoteShutdown to properly use secsWaitShutdown. */ public class SoloRemoteControl implements SocketProtocolListener, DebugListener{ public static final String listenername = "SoloRemoteControl"; /************************************************************************** * Default: C:\Program Files\Android\android-sdk\<br> * Set to the root directory where the Droid Development SDK is located. **/ public static String ROOT_DROID_SDK_DIR = "C:\\Program Files\\Android\\android-sdk\\"; /************************************************************************** * Default: C:\Program Files\Android\android-sdk\tools<br> * Set to the directory where the Droid Development SDK Tools are located. **/ public static String ROOT_DROID_SDK_TOOLS = ROOT_DROID_SDK_DIR +"tools"; protected Vector listeners = new Vector(); protected static LogsInterface log = null; protected SoloRemoteControlRunner runner = null; protected Thread runnerThread = null; public int controllerPort = SocketProtocol.DEFAULT_CONTROLLER_PORT; public boolean portForwarding = true; public static AndroidTools sdk = null; public boolean enableProtocolDebug = true; public boolean enableRunnerDebug = true; /** * No-arg constructor to instantiate and use all defaults. * The default implementation does NOT have a LogsInterface object set until a * successful call to setLogsInterface is made. * @see #setLogsInterface(LogsInterface) */ public SoloRemoteControl(){ super(); } /** * Set our static sdk tool to the one appropriate for the OS (Windows or Unix). * The routine does nothing if the appropriate sdk instance is already set. * * @see DUtilities#getAndroidTools() */ public void initAndroidTools(){ if (sdk == null){ debug("Setting Android Tools SDK Dir to "+ ROOT_DROID_SDK_DIR); sdk = DUtilities.getAndroidTools(ROOT_DROID_SDK_DIR); } } /** * Set the LogsInterface to be used by the class instance. * @param ilog */ public void setLogsInterface(LogsInterface ilog){ log = ilog; } /** * Add a listener to this Runner. Could be a DebugListener, ConnectionListener, or a * SoloRemoteControlListener--any subclass of NamedListener. * @param listen * @see NamedListener * @see ConnectionListener * @see SoloRemoteControlListener */ public void addListener(NamedListener listen){ if(! listeners.contains(listen)) listeners.add(listen); } /** * Remove a previously added listener from this Runner. * @param listen */ public void removeListener(NamedListener listen){ if(listeners.contains(listen)) listeners.remove(listen); } /** * Invoked from start(). * Creates the default instance of our Runner. * @return true to allow normal execution to proceed. * Returning false should cause an abort of the test startup procedure. * @see #start() */ protected boolean createProtocolRunner(){ runner = new SoloRemoteControlRunner(this); return true; } /** * Invoked from start(). * Creates the default instance of the runnerThread providing the current Runner * as its Runnable argument. The runnerThread is then immediately started. * @return true to allow normal execution to proceed. * Returning false should cause an abort of the test startup procedure. * @see #start() */ protected boolean startProtocolRunner() { try{ runnerThread = new Thread(runner); runnerThread.start(); return true; }catch(Exception x){ return false; } } /** * Creates and Starts the asynchronous RemoteControl runner. * @throws IllegalThreadStateException if we were unable to create and run the protocol runner. */ public void start() throws IllegalThreadStateException{ if(!createProtocolRunner()) throw new IllegalThreadStateException("Failed to create the RemoteControlRunner!"); //before we start the protocol runner, we can modify some properties of embedded #DroidSocketProtocol modifyEmbeddedProtocol(); if(!startProtocolRunner()) throw new IllegalThreadStateException("Failed to start the RemoteControlRunner!"); } /** * In this method, we can modify some properties of the embedded protocol {@link DroidSocketProtocol}.<br> * <b>Note:</b> this method MUST be called after {@link #createProtocolRunner()} * and before {@link #startProtocolRunner()}. Just like in {@link #start()}.<br> * * For example, we can modify the controllerPort as {@link DroidSocketProtocol#setControllerPort(int)}<br> * or modify portForwarding as {@link DroidSocketProtocol#setPortForwarding(boolean)}<br> * * If the 'port forwarding' is set to true, we MUST call {@link DroidSocketProtocol#adjustControllerPort()} * to choose an available port for 'controller'.<br> * * @see #start() * @see DroidSocketProtocol#setControllerPort(int) * @see DroidSocketProtocol#setPortForwarding(boolean) * @see DroidSocketProtocol#adjustControllerPort() * */ protected void modifyEmbeddedProtocol(){ if(runner!=null && runner.droidprotocolserver!=null){ runner.droidprotocolserver.setPortForwarding(portForwarding); runner.droidprotocolserver.adjustControllerPort(); }else{ debug("runner or runner.droidprotocolserver is null."); } } /** * Set the 'controller port' where we will connect for messenger service.<br> * The 'controller port' should be an available port, if it is not, it will be<br> * modified to an available one in method {@link #modifyEmbeddedProtocol()}<br> * * @param controllerPort int, an available port number * @see #modifyEmbeddedProtocol() */ public void setControllerPort(int controllerPort){ this.controllerPort = controllerPort; } /** * Set if we will forward 'controller port' to remote messenger service's port. * Must be set before {@link #start()} is called to have affect. * @param portForwarding set true if the controller should attempt port forwarding * before starting the runner thread. */ public void setPortForwarding(boolean portForwarding){ this.portForwarding = portForwarding; } /** * Command the RemoteControl infrastructure to shutdown. * @see SoloRemoteControlRunner#shutdownThread() */ public void shutdown(){ runner.shutdownThread(); } boolean remoteException = false; boolean remoteMessage = false; String remoteMessageString = null; boolean remoteReady = false; boolean remoteConnected = false; boolean remoteRunning = false; boolean remoteResult = false; int remoteResultCode = 0; String remoteResultInfo = null; Properties remoteResultProperties = null; boolean localShutdown = false; boolean remoteShutdown = false; int shutdownCause = -1; Object lock = new Object(); /** * reset the results fields and properties prior to dispatching a request. * remoteResult = false<br> * remoteResultCode = -99<br> * remoteResultInfo = null<br> * remoteResultProperties = null<br> * remoteComment = null<br> * remoteDetail = null<br> */ public void resetResults(){ remoteResult = false; remoteResultCode = Message.STATUS_REMOTERESULT_UNKNOWN; remoteResultInfo = null; remoteResultProperties = null; } /** * Used internally. Reset the ready field--normally immediately after having received it. * remoteReady = false<br> */ protected void resetReady(){ remoteReady = false; } /** * Consolidates the remoteResult, remoteResultCode, and remoteResultInfo into any remoteResultProperties. * If remoteResultProperties is null, the routine will create the Properties object before the consolidation. * remoteResultInfo will only be written to the Properties if it is a non-null String value. * The routine will NOT overwrite any of these remoteResult properties if they already exist in the * remoteResultProperties object. * @return remoteResultProperties */ protected Properties consolidateResults(){ if(remoteResultProperties == null) remoteResultProperties = new Properties(); if(! remoteResultProperties.containsKey(Message.KEY_ISREMOTERESULT)) remoteResultProperties.setProperty(Message.KEY_ISREMOTERESULT, String.valueOf(remoteResult)); if(!remoteResultProperties.containsKey(Message.KEY_REMOTERESULTCODE)) remoteResultProperties.setProperty(Message.KEY_REMOTERESULTCODE, String.valueOf(remoteResultCode)); if((!remoteResultProperties.containsKey(Message.KEY_REMOTERESULTINFO))&& remoteResultInfo != null) remoteResultProperties.setProperty(Message.KEY_REMOTERESULTINFO, remoteResultInfo); return remoteResultProperties; } public void resetRunningState(){ synchronized(lock){ resetResults(); remoteException = false; remoteRunning = false; remoteMessage = false; remoteMessageString = null; remoteShutdown = false; localShutdown = false; shutdownCause = -1; } } public void onReceiveConnection() { synchronized(lock){ remoteConnected = true; try{ lock.notifyAll();}catch(Throwable e){} } notifyConnectionListeners(); } public void onReceiveReady() { synchronized(lock){ remoteException = false; //remoteRunning = false; remoteReady = true; try{ lock.notifyAll();}catch(Throwable e){} } notifyReadyListeners(); } public void onReceiveRunning() { synchronized(lock){ remoteException = false; //remoteReady = false; remoteRunning = true; try{ lock.notifyAll();}catch(Throwable e){} } notifyRunningListeners(); } /** * Implemented here, but not currently used by this implementation. * This implementation is using ResultProperties. * @see #onReceiveResultProperties(Properties) */ public void onReceiveResult(int rc, String info) { synchronized(lock){ remoteException = false; //remoteRunning = false; remoteResultCode = rc; remoteResultInfo = info; remoteResultProperties = null; remoteResult = true; try{ lock.notifyAll();}catch(Throwable e){} } notifyResultsListeners(rc, info); } public void onReceiveResultProperties(Properties result) { synchronized(lock){ remoteException = false; //remoteRunning = false; remoteResultCode = -1; remoteResultInfo = null; remoteResultProperties = result; remoteResult = true; try{ lock.notifyAll();}catch(Throwable e){} } notifyResultPropsListeners(result); } public void onReceiveException(String message) { synchronized(lock){ //remoteRunning = false; remoteMessageString = message; remoteException = true; try{ lock.notifyAll();}catch(Throwable e){} } notifyExceptionListeners(message); } public void onReceiveMessage(String message) { synchronized(lock){ remoteException = false; //remoteRunning = false; remoteMessageString = message; remoteMessage = true; try{ lock.notifyAll();}catch(Throwable e){} } notifyMessageListeners(message); } public void onReceiveLocalShutdown(int shutdownCause) { synchronized(lock){ remoteException = false; //remoteRunning = false; this.shutdownCause = shutdownCause; localShutdown = true; try{ lock.notifyAll();}catch(Throwable e){} } notifyLocalShutdownListeners(shutdownCause); } public void onReceiveRemoteShutdown(int shutdownCause) { synchronized(lock){ remoteException = false; remoteRunning = false; this.shutdownCause = shutdownCause; remoteShutdown = true; try{ lock.notifyAll();}catch(Throwable e){} } notifyRemoteShutdownListeners(shutdownCause); } public String getListenerName() { return listenername; } public void onReceiveDebug(String message) { debug(message); } public void debug(String message){ boolean logged = false; for(int i=0;i< listeners.size(); i++){ try{ ((DebugListener)listeners.get(i)).onReceiveDebug(message); logged = true; }catch(Exception x){} } if(!logged){ try{ log.debug(message); } catch(Exception x){ System.out.println(message); } } } /** * Throw a ShutdownInvocationException only *IF* we have received a localShutdown * or remoteShutdown message. Throw a RemoteException only *IF* we have received * a remoteException. Otherwise, this routine returns doing nothing. * @throws ShutdownInvocationException if localShutdown or remoteShutdown have been * received. * @throws RemoteException if remoteException has been received. */ protected void checkExceptions() throws RemoteException, ShutdownInvocationException{ if (localShutdown){ throw new ShutdownInvocationException("Unexpected Local Shutdown has been initiated.", false, shutdownCause); }else if (remoteShutdown){ throw new ShutdownInvocationException("Unexpected Remote Shutdown has been initiated.", true, shutdownCause); }else if (remoteException){ throw new RemoteException(remoteMessageString); } } /** * Wait for the remote client to get connected. * This is typically called from an external Thread. * @param sTimeout in seconds to wait for connection. * If timeout is < 1 then the there is no wait. * * @throws TimeoutException if connection was not made in the timeout period. * @throws RemoteException if we received an unexpected remote exception instead. * @throws ShutdownInvocationException if either a local or remote shutdown has been * initiated. */ public void waitForRemoteConnected(int sTimeout)throws TimeoutException, RemoteException, ShutdownInvocationException{ if (sTimeout < 1) { // do nothing }else{ long millis = sTimeout * 1000; long maxTicks = System.currentTimeMillis() + millis; synchronized(lock){ while(!remoteConnected && !remoteException && !localShutdown && !remoteShutdown && System.currentTimeMillis() < maxTicks){ try{ lock.wait(millis); } catch(InterruptedException e){} } } } checkExceptions(); if(! remoteConnected) throw new TimeoutException("waitForRemoteConnected Timeout before Connect"); } protected void notifyConnectionListeners(){ for(int i=0;i< this.listeners.size(); i++){ try{ ((ConnectionListener)listeners.get(i)).onReceiveConnection(); }catch(Exception x){} } } /** * Wait for the remote client to signal Ready for a new command. * This is typically called from an external Thread. * @param sTimeout in seconds to wait for ready. If timeout is < 1 then there * is no wait. * * @throws TimeoutException if Ready was not seen in the timeout period. * @throws RemoteException if we received an unexpected remote exception instead. * @throws ShutdownInvocationException if either a local or remote shutdown has been * initiated. */ public void waitForRemoteReady(int sTimeout)throws TimeoutException, RemoteException, ShutdownInvocationException{ if (sTimeout < 1) { // do nothing }else{ long millis = sTimeout * 1000; long maxTicks = System.currentTimeMillis() + millis; synchronized(lock){ while(!remoteReady && !remoteException && !localShutdown && !remoteShutdown && System.currentTimeMillis() < maxTicks){ try{ lock.wait(millis); } catch(InterruptedException e){} } } } checkExceptions(); if(! remoteReady) throw new TimeoutException("waitForRemoteReady Timeout before Ready"); } protected void notifyReadyListeners(){ for(int i=0;i< this.listeners.size(); i++){ try{ ((SocketProtocolListener)listeners.get(i)).onReceiveReady(); }catch(Exception x){} } } /** * Default implementation performs the following: * <p><pre> * resetResults(); * waitForRemoteReady(secsWaitReady); * runner.sendDispatchProps(props); * waitForRemoteRunning(secsWaitRunning); * return waitForRemoteResult(secsWaitResult); * </pre> * @param props - the Dispatch Properties object containing all required command parameters for the remote client. * @param secsWaitRead -- timeout in seconds to detect remoteReady. * @param secsWaitRunning -- timeout in seconds to detect remoteRunning following the dispatch. * @param secsWaitResults -- timeout in seconds to wait for results from the remote client. * @return - the Result Properties returned by the remote client. * @throws ShutdownInvocationException * @throws TimeoutException * @throws RemoteException * @throws IllegalThreadStateException -- if the attempt to send the properties failed for some unknown reason. * @see SoloRemoteControlRunner#sendDispatchProps(Properties) */ public Properties performRemotePropsCommand(Properties props, int secsWaitReady, int secsWaitRunning, int secsWaitResult) throws IllegalThreadStateException, RemoteException, TimeoutException, ShutdownInvocationException{ resetResults(); waitForRemoteReady(secsWaitReady); resetReady(); if(runner.sendDispatchProps(props)) { waitForRemoteRunning(secsWaitRunning); return waitForRemoteResult(secsWaitResult); } throw new IllegalThreadStateException("Local ProtocolRunner failed to sendDispatchProps."); } /** * Default implementation performs the following: * <p><pre> * resetResults(); * waitForRemoteReady(secsWaitReady); * runner.sendDispatchFile(filepath); * waitForRemoteRunning(secsWaitRunning); * return waitForRemoteResult(secsWaitResult); * </pre> * @param filepath - the filepath to the File to process. * @param secsWaitRead -- timeout in seconds to detect remoteReady. * @param secsWaitRunning -- timeout in seconds to detect remoteRunning following the dispatch. * @param secsWaitResults -- timeout in seconds to wait for results from the remote client. * @return - the Result Properties returned by the remote client. * @throws ShutdownInvocationException * @throws TimeoutException * @throws RemoteException * @throws IllegalThreadStateException -- if the attempt to send failed for some unknown reason. * @see SoloRemoteControlRunner#sendDispatchProps(Properties) */ public Properties performRemoteFileCommand(String filepath, int secsWaitReady, int secsWaitRunning, int secsWaitResult) throws IllegalThreadStateException, RemoteException, TimeoutException, ShutdownInvocationException{ resetResults(); waitForRemoteReady(secsWaitReady); resetReady(); if(runner.sendDispatchFile(filepath)) { waitForRemoteRunning(secsWaitRunning); return waitForRemoteResult(secsWaitResult); } throw new IllegalThreadStateException("Local ProtocolRunner failed to sendDispatchProps."); } /** * Default implementation performs the following: * <p><pre> * resetResults(); * waitForRemoteReady(secsWaitReady); * runner.sendMessage(message); * waitForRemoteRunning(secsWaitRunning); * </pre> * <p> * The routine does NOT waitForResults. This allows arbitrary messaging not always expecting a response. * @param message to send to remote client * @param secsWaitRead -- timeout in seconds to detect remoteReady. * @param secsWaitRunning -- timeout in seconds to detect remoteRunning following the dispatch. * @param secsWaitResults -- timeout in seconds to wait for results from the remote client. * @throws ShutdownInvocationException * @throws TimeoutException * @throws RemoteException * @throws IllegalThreadStateException -- if the attempt to send the message failed for some unknown reason. * @see SoloRemoteControlRunner#sendDispatchProps(Properties) */ public void performRemoteMessageCommand(String message, int secsWaitReady, int secsWaitRunning) throws IllegalThreadStateException, RemoteException, TimeoutException, ShutdownInvocationException{ resetResults(); waitForRemoteReady(secsWaitReady); resetReady(); if(runner.sendMessage(message)) { waitForRemoteRunning(secsWaitRunning); return; } throw new IllegalThreadStateException("Local ProtocolRunner failed to sendDispatchProps."); } /** * Default implementation performs the following: * <p><pre> * resetResults(); * waitForRemoteReady(secsWaitReady); * runner.sendShutdown(); * waitForRemoteRunning(secsWaitRunning); * </pre> * <p> * The routine does NOT waitForResults. This allows arbitrary messaging not always expecting a response. * @param message to send to remote client * @param secsWaitRead -- timeout in seconds to detect remoteReady. * @param secsWaitRunning -- timeout in seconds to detect remoteRunning following the dispatch. * @param secsWaitShutdown -- timeout in seconds to wait to receive shutdown confirmation. * @throws TimeoutException * @throws RemoteException * @throws IllegalThreadStateException -- if the attempt to send the message failed for some unknown reason. * @see SoloRemoteControlRunner#sendDispatchProps(Properties) */ public void performRemoteShutdown(int secsWaitReady, int secsWaitRunning, int secsWaitShutdown) throws IllegalThreadStateException, RemoteException, TimeoutException, ShutdownInvocationException{ resetResults(); waitForRemoteReady(secsWaitReady); resetReady(); if(runner.sendShutdown()) { try{ waitForRemoteRunning(secsWaitRunning); waitForRemoteShutdown(secsWaitShutdown); return; } catch(TimeoutException x){ debug("performRemoteShutdown ignoring TimeoutException: "+ x.getMessage()); } catch(RemoteException x){ debug("performRemoteShutdown ignoring RemoteException: "+ x.getMessage()); } catch(ShutdownInvocationException x){ debug("performRemoteShutdown ignoring ShutdownInvocationException: "+ x.getMessage()); } }else{ throw new IllegalThreadStateException("Local ProtocolRunner failed to sendShutdown request."); } } /** * Wait for the remote client to signal Running a new command. * This is typically called from an external Thread. * @param sTimeout in seconds to wait for Running. If timeout is < 1 then there * is no wait. * * @throws TimeoutException if Running was not seen in the timeout period. * @throws RemoteException if we received an unexpected remote exception instead. * @throws ShutdownInvocationException if either a local or remote shutdown has been * initiated. */ public void waitForRemoteRunning(int sTimeout)throws TimeoutException, RemoteException, ShutdownInvocationException{ if (sTimeout < 1) { // do nothing }else{ long millis = sTimeout * 1000; long maxTicks = System.currentTimeMillis() + millis; synchronized(lock){ while(!remoteRunning && !remoteResult && !remoteException && !localShutdown && !remoteShutdown && System.currentTimeMillis() < maxTicks){ try{ lock.wait(millis); } catch(InterruptedException e){} } } } checkExceptions(); if(! remoteRunning && !remoteResult) throw new TimeoutException("waitForRemoteRunning Timeout before Running"); } protected void notifyRunningListeners(){ for(int i=0;i< this.listeners.size(); i++){ try{ ((SocketProtocolListener)listeners.get(i)).onReceiveRunning(); }catch(Exception x){} } } /** * Wait for the remote client to signal results are available and return those results. * This is typically called from an external Thread immediately following a "dispatch" call. * <p> * When a valid remote result has been received it is first checked for the presence of a * special property {@value Message#KEY_CHANGETIMEOUT}. If that key is present then the property * value is processed for a new timeout value and the routine waits for a new remote result using * the new timeout value. If no new timeout is provided, or the format is non-numeric, the * routine will simply wait again using the original sTimeout value. * <p> * @param sTimeout in seconds to wait for Results. If timeout is < 1 then there * is no wait. * @return Properties -- the resultProps returned from the remote client or a new Properties * object containing the consolidated remote result information. * @throws TimeoutException if Results was not seen in the timeout period. * @throws RemoteException if we received an unexpected remote exception instead. * @throws ShutdownInvocationException if either a local or remote shutdown has been * initiated. * @see #consolidateResults() */ public Properties waitForRemoteResult(int sTimeout)throws TimeoutException, RemoteException, ShutdownInvocationException{ if (sTimeout < 1) { // do nothing }else{ long millis = sTimeout * 1000; long maxTicks = System.currentTimeMillis() + millis; synchronized(lock){ while(!remoteResult && !remoteException && !localShutdown && !remoteShutdown && System.currentTimeMillis() < maxTicks){ try{ lock.wait(millis); } catch(InterruptedException e){} } } } checkExceptions(); if(! remoteResult) throw new TimeoutException("waitForRemoteResult Timeout before Result"); remoteResultProperties = consolidateResults(); if(remoteResultProperties.containsKey(Message.KEY_CHANGETIMEOUT)){ try{ int newtimeout = Integer.valueOf(remoteResultProperties.getProperty(Message.KEY_CHANGETIMEOUT)); if (newtimeout > 0) { debug("waitForRemoteResult detecting device-side timeout extension of "+ newtimeout); resetResults(); return waitForRemoteResult(newtimeout); }else{ debug("waitForRemoteResult ignoring invalid device-side timeout extension of "+ remoteResultProperties.getProperty(Message.KEY_CHANGETIMEOUT)); } }catch(Exception x){ debug("waitForRemoteResult ignoring "+ x.getClass().getSimpleName()+" and relooping..."); resetResults(); return waitForRemoteResult(sTimeout); } } return remoteResultProperties; } protected void notifyResultsListeners(int rc, String info){ for(int i=0;i< this.listeners.size(); i++){ try{ ((SocketProtocolListener)listeners.get(i)).onReceiveResult(rc, info); }catch(Exception x){} } } protected void notifyResultPropsListeners(Properties props){ for(int i=0;i< this.listeners.size(); i++){ try{ ((SocketProtocolListener)listeners.get(i)).onReceiveResultProperties(props); }catch(Exception x){} } } /** * Wait for the remote client to signal its shutdown. * This is typically called from an external Thread. * @param sTimeout in seconds to wait for shutdown. If timeout is < 1 then there * is no wait. * * @throws TimeoutException if shutdown was not seen in the timeout period. * @throws RemoteException if we received an unexpected remote exception instead. */ public void waitForRemoteShutdown(int sTimeout)throws TimeoutException, RemoteException{ if (sTimeout < 1) { // do nothing }else{ long millis = sTimeout * 1000; long maxTicks = System.currentTimeMillis() + millis; synchronized(lock){ while(!remoteShutdown && !remoteException && !localShutdown && System.currentTimeMillis() < maxTicks){ try{ lock.wait(millis); } catch(InterruptedException e){} } } } if(remoteException) throw new RemoteException(remoteMessageString); if(! (remoteShutdown || localShutdown)) throw new TimeoutException("waitForRemoteShutdown Timeout before Shutdown"); } protected void notifyRemoteShutdownListeners(int cause){ for(int i=0;i< this.listeners.size(); i++){ try{ ((SocketProtocolListener)listeners.get(i)).onReceiveRemoteShutdown(cause); }catch(Exception x){} } } protected void notifyLocalShutdownListeners(int cause){ for(int i=0;i< this.listeners.size(); i++){ try{ ((SocketProtocolListener)listeners.get(i)).onReceiveLocalShutdown(cause); }catch(Exception x){} } } protected void notifyExceptionListeners(String message){ for(int i=0;i< this.listeners.size(); i++){ try{ ((SocketProtocolListener)listeners.get(i)).onReceiveException(message); }catch(Exception x){} } } protected void notifyMessageListeners(String message){ for(int i=0;i< this.listeners.size(); i++){ try{ ((SocketProtocolListener)listeners.get(i)).onReceiveMessage(message); }catch(Exception x){} } } }