/**
** Copyright (C) SAS Institute, All rights reserved.
** General Public License: http://www.opensource.org/licenses/gpl-license.php
**/
package org.safs.android.messenger;
import org.safs.sockets.DebugListener;
import org.safs.sockets.SocketProtocol;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.util.Log;
/**
* TCP Messenger Service acting as intermediary between a remote test controller communicating over TCP Sockets
* and an Android test package expecting two-way communication for receiving test commands and returning
* test results and data. This Service allows a test package with no INTERNET permissions to effectively be
* remotely controlled via TCP Sockets.
* <p>
* This Service handles the TCP Sockets communication with the remote test controller and forwards all data
* exchanges with the local test package via the Inter-Process Communication Messenger facilities provided
* by the Android OS.
* <p>
* Currently, this service is accepting connections on port 2410.
* Currently, the remote controller server uses port 2411 to contact and attempt a connection to this service.
* Both sides eventually need to be able to use a broader range of ports to prevent conflicts with
* other system resources.
* <p>
* When using the Android Emulator, the emulator must be configured to "see" Socket requests on its local
* port 2410 coming from the controller on port 2411. Do this with the adb forwarding command to the running
* emulator as follows:
* <p>
* adb forward tcp:2411 tcp:2410
* <p>
* There is an initial handshake or verification that occurs between the remote controller server and
* this Service to confirm the device port owner is a SAFS TCP Messenger Service.
*
* @see org.safs.sockets.RemoteClientRunner
* @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 MessengerService extends Service implements RemoteClientListener,
DebugListener,
MessengerListener
{
public static final String TAG = "SAFSMessenger";
public static final String KEY_ENGINE_NAME="ENGINE_NAME";
public static final CharSequence NOTIFICATION_TEXT = "SAFS Messenger Service";
public static final int NOTIFICATION_DEFAULT_ID = 1;
NotificationManager mNM;
RemoteClientRunner tcpServer;
Looper mServiceLooper;
MessengerHandler mServiceHandler;
Messenger serviceMessenger;
Messenger engineMessenger;
org.safs.sockets.Message safs = new org.safs.sockets.Message();
protected void debug(String text){
onReceiveDebug(text);
}
public void onReceiveDebug(String text){
Log.d(TAG, text);
}
/**
* Show an Android on-device Notification while running....
*/
private void showNotification(){
Notification notification = new Notification(R.drawable.bidi_arrows,
NOTIFICATION_TEXT,
System.currentTimeMillis());
//Intent notificationIntent = new Intent(this, StartupActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, MessengerService.class),0);
notification.setLatestEventInfo(this, NOTIFICATION_TEXT, NOTIFICATION_TEXT, pendingIntent);
startForeground(NOTIFICATION_DEFAULT_ID, notification);
}
/**
* @see android.app.Service#onBind(android.content.Intent)
*/
@Override
public void onCreate() {
HandlerThread thread = new HandlerThread("SAFSMessengerService", android.os.Process.THREAD_PRIORITY_FOREGROUND);
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new MessengerHandler(mServiceLooper, this);
serviceMessenger = new Messenger(mServiceHandler);
mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
showNotification();
tcpServer = new RemoteClientRunner(this);
new Thread(tcpServer).start();
}
/**
* Automatically called when the Service is being started up--usually from the test package engine
* requesting to bind with the Service.
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId){
android.os.Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
mServiceHandler.sendMessage(msg);
//return START_STICKY;
return START_NOT_STICKY;
}
/**
* Capture the Message handler for the test package that is binding with the Service.
* @see android.app.Service#onBind(android.content.Intent)
*/
@Override
public IBinder onBind(Intent intent) {
return serviceMessenger.getBinder();
}
/**
* Cancel any Notification we have as we die and shutdown our SocketServer.
* @see android.app.Service#onDestroy()
*/
@Override
public void onDestroy() {
tcpServer.shutdownThread();
tcpServer = null;
debug("Service has been destroyed!");
mNM.cancel(NOTIFICATION_DEFAULT_ID);
}
protected void sendTCPMessage(String message){
debug("sendTCPMessage: "+ message);
try{ tcpServer.sendProtocolMessage(message);}
catch(Exception x){
debug("sendTCPMessage "+ x.getClass().getSimpleName()+", "+ x.getMessage());
}
}
public String getListenerName() {
return tcpServer.getListenerName();
}
//***********************************************************************
// Begin MessengerListener API
//***********************************************************************
public void onMessengerDebug(String message) {
debug(message);
}
public void onEngineDebug(String message) {
sendTCPMessage(safs.msg_debug + safs.msg_sep + message);
}
public void onEngineException(String message) {
sendTCPMessage(safs.msg_exception + safs.msg_sep + message);
}
public void onEngineMessage(String message) {
sendTCPMessage(safs.msg_message + safs.msg_sep + message);
}
public void onEngineResult(int statuscode, String statusinfo) {
sendTCPMessage(safs.msg_result + safs.msg_sep + String.valueOf(statuscode).trim() +
safs.msg_sep + statusinfo);
}
public void onEngineResultProps(char[] props) {
sendTCPMessage(safs.msg_resultprops + safs.msg_sep + String.valueOf(props));
}
public void onEngineShutdown(int cause) {
if(cause == SocketProtocol.STATUS_SHUTDOWN_NORMAL)
sendTCPMessage(safs.msg_remoteshutdown);
else
sendTCPMessage(safs.msg_shutdown);
}
public void onEngineReady() {
sendTCPMessage(safs.msg_ready);
}
public void onEngineRegistered(Messenger messenger) {
debug("received EngineRegistered notification...");
engineMessenger = messenger;
}
public void onEngineUnRegistered() {
engineMessenger = null;
sendTCPMessage(safs.msg_remoteshutdown);
stopSelf();
}
public void onEngineRunning() {
sendTCPMessage(safs.msg_running);
}
//***********************************************************************
// Begin SocketServerListener API
//***********************************************************************
protected void sendIPCMessage(int what){
Message msg = mServiceHandler.obtainMessage(what);
msg.replyTo = serviceMessenger;
try{
debug("sending IPC Message ID: "+ what);
engineMessenger.send(msg);
}
catch(Exception x){
debug("sendIPCEvent "+ x.getClass().getSimpleName()+", "+ x.getMessage());
}
}
protected void sendServiceParcelAcknowledge(String messageID, int index){
Message msg = mServiceHandler.obtainMessage(MessageUtil.ID_PARCEL_ACKNOWLEDGMENT);
msg.replyTo = serviceMessenger;
msg.arg1 = index;
try{
debug("sending IPC parcel acknowledge: "+ messageID +", "+ index);
if(messageID!=null) msg.obj = MessageUtil.setParcelableMessage(messageID);
engineMessenger.send(msg);
}
catch(Exception x){
debug("sendIPCEvent "+ x.getClass().getSimpleName()+", "+ x.getMessage());
}
}
protected void sendIPCMessage(int what, String msgobj){
Message msg = mServiceHandler.obtainMessage(what);
msg.replyTo = serviceMessenger;
try{
debug("sending IPC Message ID: "+ what +", "+ msgobj);
mServiceHandler.sendMessageAsMultipleParcels(engineMessenger, msg, msgobj);
}
catch(Exception x){
debug("sendIPCString "+ x.getClass().getSimpleName()+", "+ x.getMessage());
}
}
protected void sendIPCMessage(int what, char[] props){
Message msg = mServiceHandler.obtainMessage(what);
msg.replyTo = serviceMessenger;
try{
debug("sending IPC Message ID: "+ what +", "+ String.valueOf(props));
mServiceHandler.sendMessageAsMultipleParcels(engineMessenger, msg, props);
}
catch(Exception x){
debug("sendIPCProps "+ x.getClass().getSimpleName()+", "+ x.getMessage());
}
}
public void onReceiveDispatchFile(String filepath) {
sendIPCMessage(MessageUtil.ID_ENGINE_DISPATCHFILE, filepath);
}
public void onReceiveDispatchProps(char[] props) {
sendIPCMessage(MessageUtil.ID_ENGINE_DISPATCHPROPS, props);
}
public void onReceiveMessage(String message) {
sendIPCMessage(MessageUtil.ID_ENGINE_MESSAGE, message);
}
public void onReceiveConnection() {
sendIPCMessage(MessageUtil.ID_SERVER_CONNECTED);
}
/**
* The controller has requested a shutdown.
*/
public void onReceiveRemoteShutdown(int cause) {
sendIPCMessage(MessageUtil.ID_ENGINE_SHUTDOWN);
}
/**
* The service has shutdown. Catastrophic?
*/
public void onReceiveLocalShutdown(int cause) {
sendIPCMessage(MessageUtil.ID_SERVER_SHUTDOWN);
}
public void onAllParcelsHaveBeenHandled(String messageID) {
sendIPCMessage(MessageUtil.ID_ALL_PARCELS_ACKNOWLEDGMENT, messageID);
}
public void onParcelHasBeenHandled(String messageID, int index){
sendServiceParcelAcknowledge(messageID, index);
}
}