/** ** Copyright (C) SAS Institute, All rights reserved. ** General Public License: http://www.opensource.org/licenses/gpl-license.php **/ package org.safs.android.messenger.client; import java.io.CharArrayWriter; import java.io.FileReader; import java.io.IOException; import java.util.Properties; import org.safs.android.messenger.MessageUtil; import org.safs.sockets.DebugListener; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.util.Log; /** * It is used to handle the messages from 'TCP Messenger' in a separated Thread. * * @see com.jayway.android.robotium.remotecontrol.client.AbstractTestRunner * @see com.jayway.android.robotium.remotecontrol.client.RobotiumTestRunner * @author Carl Nagle, SAS Institute, Inc. * @since FEB 04, 2012 (CANAGL) Initial version * <br> APR 25, 2013 (LeiWang) Handle message of big size. */ public class MessengerRunner implements MessengerListener, Runnable{ public static final String listenerName = "MessengerRunner"; boolean messageHandled = false; Properties trd_props = null; String trd_message = null; int iNotification = -99; /** * messageHandler is used to handle the received message from 'TCP Messenger' */ MessengerHandler messageHandler = new MessengerHandler(this); /** * mMessenger is used to receive message from message-service server */ final Messenger mMessenger = new Messenger(messageHandler); /** * mService is the messenger to be used to send out message to 'TCP Messenger' */ Messenger mService = null; CommandListener commandListener = null; DebugListener debugListener = null; private boolean keeprunning = true; public MessengerRunner(Messenger mService){ this.mService = mService; } public MessengerRunner(Messenger mService, CommandListener commandListener){ this(mService); this.commandListener = commandListener; if(commandListener instanceof DebugListener) debugListener = (DebugListener) commandListener; } public String getListenerName(){ return listenerName; } public Messenger getmService() { return mService; } public void setmService(Messenger mService) { this.mService = mService; } public CommandListener getCommandListener() { return commandListener; } public void setCommandListener(CommandListener commandListener){ this.commandListener = commandListener; } public DebugListener getDebugListener() { return debugListener; } public void setDebugListener(DebugListener debugListener){ this.debugListener = debugListener; } public void debug(String message){ if(debugListener!=null){ debugListener.onReceiveDebug(message); }else{ Log.d(listenerName, message); } } public void onReceiveDebug(String message){ debug(message); } /** MessengerHandler preparing information for Thread switching. */ public void prepareNotification(int what){ iNotification = what; } public void onRemoteDispatchProps(Properties props){ debug("Listener received remoteDispatchProps..."); trd_message = null; trd_props = props; messageHandled = false; synchronized(this){ this.notifyAll(); } } public void onRemoteDispatchFile(String message){ debug("Listener received remoteDispatchFile..."); trd_message = null; trd_props = new Properties(); try{ trd_props.load(new FileReader(message)); messageHandled = false; debug("Properties loaded from File!"); synchronized(this){ this.notifyAll(); } }catch(IOException x){ debug("onRemoteDispatchFile "+ x.getClass().getSimpleName()+", "+x.getMessage()); } } public void onRemoteMessage(String message){ debug("Listener received remoteMessage..."); trd_props = null; trd_message = message; messageHandled = false; synchronized(this){ this.notifyAll(); } } public void onRemoteConnected(){ debug("Listener received remoteConnected..."); trd_props = null; trd_message = null; messageHandled = false; synchronized(this){ this.notifyAll(); } } public void onRemoteDisconnected(){ debug("Listener received remoteDisconnected..."); trd_props = null; trd_message = null; messageHandled = false; synchronized(this){ this.notifyAll(); } } /** Notification that the Remote Controller has shutdown and is no longer available. */ public void onRemoteShutdown(){ debug("Listener received remoteShutdown notice..."); trd_props = null; trd_message = null; messageHandled = false; synchronized(this){ this.notifyAll(); } } /** Notification that the Messenger Service has shutdown and is no longer available. */ public void onServiceShutdown(){ debug("Listener received serviceShutdown..."); trd_props = null; trd_message = null; messageHandled = false; synchronized(this){ this.notifyAll(); } } /** Remote request/command to tell the engine to perform a normal shutdown. */ public void onRemoteEngineShutdown(){ debug("Listener received remoteShutdown command..."); trd_props = null; trd_message = null; messageHandled = false; synchronized(this){ this.notifyAll(); } } /** * Primary looping test thread remains active for as long as we are bound to a TCP Messenger Service. * Synchronizes with the Message handler waiting for new valid instructions then routes test * actions according to the content of the data received via the Messenger. */ public void run() { MessageResult result = null; while(keeprunning){ synchronized(this){ try{ this.wait(); }catch(InterruptedException x){} } if(!messageHandled){ messageHandled = true; switch (iNotification){ case MessageUtil.ID_ENGINE_DISPATCHPROPS: sendRunning(); debug("Handler Received DispatchProps!"); //handle the request commandListener.handleDispatchProps(trd_props); sendServiceResult(trd_props); sendReady(); break; case MessageUtil.ID_ENGINE_DISPATCHFILE: sendRunning(); debug("Handler Received DispatchFile!"); //handle the request commandListener.handleDispatchProps(trd_props); sendServiceResult(trd_props); sendReady(); break; case MessageUtil.ID_ENGINE_SHUTDOWN: sendRunning(); debug("Handler Received Shutdown Command!"); //If the shutdown command comes, we will stop running of this thread. keeprunning=false; this.stop(); //Do we need to send the shutdown command back??? sendShutdown(); //Invoke the callback of commandListener, let it know the messengerRunner has stopped. result = commandListener.handleEngineShutdown(); if(result != null){ debug(result.getStatusinfo()); sendServiceResult(result); } break; case MessageUtil.ID_ENGINE_MESSAGE: sendRunning(); result = commandListener.handleMessage(trd_message); if(result != null){ debug(result.getStatusinfo()); sendServiceResult(result); } sendReady(); break; case MessageUtil.ID_SERVER_CONNECTED: debug("Handler received CONNECTED message: "+ trd_message); result = commandListener.handleServerConnected(); if(result != null){ debug(result.getStatusinfo()); sendServiceResult(result); } sendReady(); break; } } } commandListener.messengerRunnerStopped(); } public boolean start(){ if(mService==null){ debug("The mService is null, please initialize it with setMService()"); return false; } if(commandListener==null){ debug("The commandListener is null, please initialize it with setCommandListener()"); return false; } Thread thread = new Thread(this); thread.start(); return true; } public void stop(){ this.keeprunning = false; } /* * Pseudo SocketProtocolListener Interface follows * These are the methods used by the device-side engine to send results or other data to the * TCP Messenger service which ultimately sends them over Sockets to the remote test controller. */ /** * Note: NEVER call debug() in this method or in the method it will call!!! It causes StackOverFlow. * Create and send any String message through the service to the remote test controller. * Most of the other methods of this pseudo-interface call this method with appropriate parameters. * @param what, the message int flagging what kind of message we are sending. * @param message, the actual message--which can be null. * @return true on successfully sent */ boolean sendServiceMessage(int what, String message){ Message msg; if(message != null){ msg = Message.obtain(null, what, MessageUtil.setParcelableMessage(message)); }else{ msg = Message.obtain(null, what); } msg.replyTo = mMessenger; try{ Log.d(listenerName, "Engine sending message: "+ message); mService.send(msg); return true; }catch(RemoteException x){ Log.d(listenerName, "Failed to send to MessengerService due to "+ x.getClass().getSimpleName()+", "+ x.getMessage()); } return false; //TODO Will string message exceeds the buffer size??? send it as multiple parcels??? //If this is needed, un-comment following codes, but remember to change use of debug() //to Log.d() // Message msg = Message.obtain(null, what); // msg.replyTo = mMessenger; // Log.d(listenerName, "Engine sending message: "+ message); // // return multipleParcelsHandler.sendMessageAsMultipleParcels(mService, msg, message); } public boolean sendServiceResult(MessageResult result){ return sendServiceResult(result.getStatuscode(), result.getStatusinfo()); } /** * Create and send a MSG_ENGINE_RESULT message with the int statuscode and String statusinfo * result from processing a previous command/dispatch. This is largely used when we cannot send * result Properties instead. * @return true on successfully sent * @see #sendServiceResult(Properties) */ public boolean sendServiceResult(int statuscode, String statusinfo){ Message msg = Message.obtain(null, MessageUtil.ID_ENGINE_RESULT); msg.replyTo = mMessenger; msg.arg1 = statuscode; debug("Engine sending simple result: "+ statuscode +", "+ statusinfo); try { if(messageHandler!=null) return messageHandler.sendMessageAsMultipleParcels(mService, msg, statusinfo); else{ if(statusinfo!=null) msg.obj = MessageUtil.setParcelableMessage(statusinfo); mService.send(msg); return true; } } catch (RemoteException x) { debug(listenerName + ": Failed to send to MessengerService due to " + org.safs.sockets.Message.getStackTrace(x)); } return false; } public boolean sendServiceParcelAcknowledge(String messageID, int index){ Message msg = Message.obtain(null, MessageUtil.ID_PARCEL_ACKNOWLEDGMENT); msg.replyTo = mMessenger; debug("Engine sending parcel acknowledge: "+ messageID +", "+ index); msg.arg1 = index; try { if(messageID!=null) msg.obj = MessageUtil.setParcelableMessage(messageID); mService.send(msg); return true; } catch (RemoteException x) { debug(listenerName + ": Failed to send to MessengerService due to " + org.safs.sockets.Message.getStackTrace(x)); } return false; } /** * Create and send a MSG_ENGINE_RESULTPROPS message with the Properties * result from processing a previous command/dispatch. This is the preferred way to return results * since more information can be transferred. * @return true on successfully sent * @see #sendServiceResult(int,String) */ public boolean sendServiceResult(Properties props){ return sendServiceProperties(MessageUtil.ID_ENGINE_RESULTPROPS, props); } public boolean sendServiceProperties(int what, Properties props){ Message msg = Message.obtain(null, what); msg.replyTo = mMessenger; debug("Engine sending result Propertie."); try { if(messageHandler!=null) return messageHandler.sendMessageAsMultipleParcels(mService, msg, props); else{ CharArrayWriter chars = new CharArrayWriter(); props.store(chars, "ResultProperties"); char[] buffer = chars.toCharArray(); msg.obj = MessageUtil.setParcelableProps(buffer); mService.send(msg); return true; } } catch (Exception x) { debug(listenerName + ": Failed to send to MessengerService due to " + org.safs.sockets.Message.getStackTrace(x)); } return false; } /** * Create and send a MSG_ENGINE_SHUTDOWN signaling the engine has or is in the process of shutting down. * @return true on successfully sent * @see #sendServiceMessage(int, String) */ public boolean sendShutdown(){ debug("sendShutdown...(null)"); return sendServiceMessage(MessageUtil.ID_ENGINE_SHUTDOWN, null); } /** * Create and send a MSG_ENGINE_READY signaling the engine is ready to process remote commands. * @return true on successfully sent * @see #sendServiceMessage(int, String) */ public boolean sendReady(){ debug("sendReady...(null)"); return sendServiceMessage(MessageUtil.ID_ENGINE_READY, null); } /** * Create and send a MSG_ENGINE_RUNNING signaling the engine is processing the remote command. * @return true on successfully sent * @see #sendServiceMessage(int, String) */ public boolean sendRunning(){ debug("sendRunning...(null)"); return sendServiceMessage(MessageUtil.ID_ENGINE_RUNNING, null); } /** * Create and send a MSG_ENGINE_DEBUG message. * This allows the engine to route debug messages to * external debug logging mechanisms handled by the remote test controller. * @return true on successfully sent * @see #sendServiceMessage(int, String) */ public boolean sendDebug(String message){ //Log.d(listenerName, "sendDebug..."); /* infinite loop for some debugListeners if sent to debug() */ return sendServiceMessage(MessageUtil.ID_ENGINE_DEBUG, message); } /** * Create and send a MSG_SERVER_MESSAGE signaling the engine has sent a custom/arbitrary message or response * to the remote test controller. It is the controller\engine developer that provides the logic to send and receive * these messages and to know what to do with them when received. * @return true on successfully sent * @see #sendServiceMessage(int, String) */ public boolean sendMessage(String message){ debug("sendMessage..."); return sendServiceMessage(MessageUtil.ID_ENGINE_MESSAGE, message); } /** * Create and send a MSG_ENGINE_EXCEPTION signaling the engine has detected and caught an Exception and * is reporting that to the remote test controller. * @return true on successfully sent * @see #sendServiceMessage(int, String) */ public boolean sendException(String message){ debug("sendException..."); return sendServiceMessage(MessageUtil.ID_ENGINE_EXCEPTION, message); } /** * Create and send a ID_REGISTER_ENGINE signaling the engine is registered. * @return true on successfully sent * @see #sendServiceMessage(int, String) */ public boolean sendRegisterEngine(){ debug("sendRegisterEngine..."); return sendServiceMessage(MessageUtil.ID_REGISTER_ENGINE, null); } /** * Create and send a ID_UNREGISTER_ENGINE signaling the engine is un-registered. * @return true on successfully sent * @see #sendServiceMessage(int, String) */ public boolean sendUnRegisterEngine(){ debug("sendUnRegisterEngine..."); return sendServiceMessage(MessageUtil.ID_UNREGISTER_ENGINE, null); } public void onAllParcelsHaveBeenHandled(String messageID) { sendServiceMessage(MessageUtil.ID_ALL_PARCELS_ACKNOWLEDGMENT, messageID); } public void onParcelHasBeenHandled(String messageID, int index){ sendServiceParcelAcknowledge(messageID, index); } }