/* * FrontlineSMS <http://www.frontlinesms.com> * Copyright 2007, 2008 kiwanja * * This file is part of FrontlineSMS. * * FrontlineSMS is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or (at * your option) any later version. * * FrontlineSMS 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. * * You should have received a copy of the GNU Lesser General Public License * along with FrontlineSMS. If not, see <http://www.gnu.org/licenses/>. */ package net.frontlinesms.messaging.sms; import java.util.*; import java.util.concurrent.*; import serial.*; import net.frontlinesms.CommUtils; import net.frontlinesms.FrontlineUtils; import net.frontlinesms.data.domain.FrontlineMessage; import net.frontlinesms.data.domain.FrontlineMessage.Status; import net.frontlinesms.events.EventBus; import net.frontlinesms.listener.SmsListener; import net.frontlinesms.messaging.CommProperties; import net.frontlinesms.messaging.sms.events.NoSmsServicesConnectedNotification; import net.frontlinesms.messaging.sms.internet.SmsInternetService; import net.frontlinesms.messaging.sms.modem.SmsModem; import net.frontlinesms.messaging.sms.modem.SmsModemStatus; import org.apache.log4j.Logger; import org.smslib.CIncomingMessage; import org.smslib.handler.CATHandler; import org.smslib.util.GsmAlphabet; /** * SmsHandler should be run as a separate thread. * * It handles the discovery of phones available on the system's COM ports, * and also manages a pool of threads that handle the communication with as many phones as are found * attached to the system. * * Autodetection should take 30 seconds. * * OUTGOING MESSAGES * When you send a new outgoing message through SmsHandler it is added to a stack of waiting messages, * which it will then send to the waiting phones by turn, unless the messages are marked as being * for a specific phone. * * INCOMING MESSAGES * If you create SmsHandler and pass it an SmsListener, incoming messages will be reported as events * to that listener. If you create the SmsHandler without the listener, the messages will just appear * on the linked list of IncomingMessages, and the calling program must poll it for new messages. * * Incoming messages are immediately removed from active phones, so if you close the program without * storing the message, then you will have lost the message. * * PHONE STATE: * When a phone handler is created on a port, it will attempt AT commands until it gets an OK from a modem. * A valid OK will make the phoneHandler set phonePresent to TRUE. * The PhoneHanler will then attempt to connect the full SMSLIB tools to it, to take it into * connected=true state, from which you can actually send and recieve messages. * * HTTP services: * In the future this will be extended to be able to interface with * internet based SMS services via HTTP, to handle bulk messaging. * * @author Ben Whitaker ben(at)masabi(dot)com * @author Alex Anderson alex(at)masabi(dot)com */ public class SmsServiceManager extends Thread implements SmsListener { /** List of GSM 7bit text messages queued to be sent. */ private final ConcurrentLinkedQueue<FrontlineMessage> gsm7bitOutbox = new ConcurrentLinkedQueue<FrontlineMessage>(); /** List of UCS2 text messages queued to be sent. */ private final ConcurrentLinkedQueue<FrontlineMessage> ucs2Outbox = new ConcurrentLinkedQueue<FrontlineMessage>(); /** List of binary messages queued to be sent. */ private final ConcurrentLinkedQueue<FrontlineMessage> binOutbox = new ConcurrentLinkedQueue<FrontlineMessage>(); /** List of phone handlers that this manager is currently looking after. */ private final ConcurrentMap<String, SmsModem> phoneHandlers = new ConcurrentHashMap<String, SmsModem>(); /** Set of SMS internet services */ private Set<SmsInternetService> smsInternetServices = new CopyOnWriteArraySet<SmsInternetService>(); /** Listener to be passed SMS Listener events from this */ private SmsListener smsListener; /** Listener for application events */ private EventBus eventBus; /** Flag indicating that the thread should continue running. */ private boolean running; /** If set TRUE, then thread will automatically try to connect to newly-detected devices. */ private boolean autoConnectToNewPhones; private boolean refreshPhoneList; /** * Set containing all serial numbers of discovered phones. Necessary because bluetooth/USB * devices may present multiple virtual COM ports to the app. */ private final HashSet<String> connectedSerials = new HashSet<String>(); private String[] portIgnoreList; /** Counter used for choosing which SMS device to send messages with next. * TODO we should use different counters for different types of messages, and also * for SMS internet services vs. phones. */ private int globalDispatchCounter; private static Logger LOG = FrontlineUtils.getLogger(SmsServiceManager.class); /** * Create a polling-variant SMS Handler. * To add a message listener, setSmsListener() should be called. */ public SmsServiceManager() { super("SmsDeviceManager"); // Load the COMM properties file, and extract the IGNORE list from // it - this is a list of COM ports that should be ignored. this.portIgnoreList = CommProperties.getInstance().getIgnoreList(); } public void setSmsListener(SmsListener smsListener) { this.smsListener = smsListener; } public void setEventBus(EventBus eventBus) { this.eventBus = eventBus; } public void run() { LOG.trace("ENTER"); running = true; while (running) { // Sleep for a second to ensure lists are not constantly being reshuffled. Processing dispatch // and received messages is not really time-critical, otherwise it might be worth sleeping for // less time. FrontlineUtils.sleep_ignoreInterrupts(1000); doRun(); } LOG.trace("EXIT"); } /** * Run the looped behaviour from {@link #run()} once. * This method is separated for simple, unthreaded unit testing. * THREAD: SmsDeviceManager */ void doRun() { if (refreshPhoneList) { LOG.debug("Refreshing phone list..."); // N.B. why is this not using the value from autoConnectToNewPhones? listComPortsAndOwners(autoConnectToNewPhones); refreshPhoneList = false; } else { dispatchSms(MessageType.GSM7BIT_TEXT); dispatchSms(MessageType.UCS2_TEXT); dispatchSms(MessageType.BINARY); processModemReceiving(); } } /** Handle the steps necessary when disconnecting a modem. */ private void handleDisconnect(SmsModem modem) { modem.disconnect(); } /** * list com ports, optionally find phones, and optionally connect to them * @param autoDiscoverPhones - if false, the call will only enumerate COM ports and find the owners - not try to auto-detect phones * @param connectToAllDiscoveredPhones - only works if findPhoneNames is true, and will try to not connect to duplicate connections to the same phone. */ public void refreshPhoneList(boolean autoConnectToNewPhones) { this.autoConnectToNewPhones = autoConnectToNewPhones; refreshPhoneList = true; } /** * Scan through the COM ports this computer is displaying. * When an unowned port is found, we initiate a PhoneHandler * detect an AT device on this port. Ignore all non-serial * ports and all ports whose names' appear on our "ignore" * list. * @param findPhoneNames * @param connectToAllDiscoveredPhones */ public void listComPortsAndOwners(boolean connectToAllDiscoveredPhones) { LOG.trace("ENTER"); Enumeration<CommPortIdentifier> portIdentifiers = CommUtils.getPortIdentifiers(); if (!portIdentifiers.hasMoreElements()) { if(this.eventBus != null) { this.eventBus.notifyObservers(new NoSmsServicesConnectedNotification(false, false)); } } else { LOG.debug("Getting ports..."); while (portIdentifiers.hasMoreElements()) { requestConnect(portIdentifiers.nextElement(), connectToAllDiscoveredPhones); } } LOG.trace("EXIT"); } /** * Checks if a COM port should be ignored (rather than connected to). * @param comPortName * @return */ private boolean shouldIgnore(String comPortName) { for (String ig : portIgnoreList) { if (ig.equalsIgnoreCase(comPortName)) return true; } return false; } /** * Request that an SMS with the specified text be sent to the requested * number. * @param targetNumber * @param smsMessage * @return the Message object */ public void sendSMS(FrontlineMessage outgoingMessage) { LOG.trace("ENTER"); outgoingMessage.setStatus(Status.OUTBOX); switch(MessageType.get(outgoingMessage)) { case BINARY: binOutbox.add(outgoingMessage); LOG.debug("Message added to binOutbox. Size is [" + binOutbox.size() + "]"); break; case GSM7BIT_TEXT: gsm7bitOutbox.add(outgoingMessage); LOG.debug("Message added to gsm7bitOutbox. Size is [" + gsm7bitOutbox.size() + "]"); break; case UCS2_TEXT: ucs2Outbox.add(outgoingMessage); LOG.debug("Message added to ucs2Outbox. Size is [" + ucs2Outbox.size() + "]"); break; default: throw new IllegalStateException(); } if (smsListener != null) smsListener.outgoingMessageEvent(null, outgoingMessage); LOG.trace("EXIT"); } /** * Remove the supplied message from outbox. * @param deleted */ public void removeFromOutbox(FrontlineMessage deleted) { if(gsm7bitOutbox.remove(deleted)) { if(LOG.isDebugEnabled()) LOG.debug("Message [" + deleted + "] removed from gsm7bitOutbox. Size is [" + gsm7bitOutbox.size() + "]"); } else if(ucs2Outbox.remove(deleted)) { if(LOG.isDebugEnabled()) LOG.debug("Message [" + deleted + "] removed from uc2Outbox. Size is [" + ucs2Outbox.size() + "]"); } else if(binOutbox.remove(deleted)) { if(LOG.isDebugEnabled()) LOG.debug("Message [" + deleted + "] removed from binOutbox. Size is [" + binOutbox.size() + "]"); } else { if(LOG.isInfoEnabled()) LOG.info("Attempt to delete message found in no outbox."); } } /** * Flags the internal thread to stop running. */ public void stopRunning() { this.running = false; // Disconnect all phones. for(SmsModem p : phoneHandlers.values()) { p.setDetecting(false); p.setAutoReconnect(false); handleDisconnect(p); } // Stop all SMS Internet Services for(SmsInternetService service : this.smsInternetServices) { service.stopThisThing(); } } public void incomingMessageEvent(SmsService receiver, CIncomingMessage msg) { // If we've got a higher-level listener attached to this, pass the message // up to there. Otherwise, add it to our internal list if (smsListener != null) smsListener.incomingMessageEvent(receiver, msg); } public void outgoingMessageEvent(SmsService sender, FrontlineMessage msg) { if (smsListener != null) smsListener.outgoingMessageEvent(sender, msg); if (msg.getStatus() == Status.FAILED) { if (msg.getRetriesRemaining() > 0) { msg.setRetriesRemaining(msg.getRetriesRemaining() - 1); msg.setSenderMsisdn(""); sendSMS(msg); } } } public boolean hasPhoneConnected(String port) { SmsService phoneHandler = phoneHandlers.get(port); return phoneHandler != null && phoneHandler.isConnected(); } /** * called when one of the SMS devices (phones or http senders) has a change in status, * such as detection, connection, disconnecting, running out of batteries, etc. * see PhoneHandler.STATUS_CODE_MESSAGES[smsDeviceEventCode] to get the relevant messages * * @param activeDevice * @param smsDeviceEventCode */ public void smsDeviceEvent(SmsService device, SmsServiceStatus deviceStatus) { LOG.trace("ENTER"); // Special handling for modems if (device instanceof SmsModem) { LOG.debug("Event [" + deviceStatus + "]"); SmsModem activeDevice = (SmsModem) device; if(deviceStatus.equals(SmsModemStatus.DISCONNECTED)) { // A device has just disconnected. If we aren't using the device for sending or receiving, // then we should just ditch it. However, if we *are* actively using the device, then we // would probably want to attempt to reconnect. Also, if we were previously connected to // this device then we should now remove its serial number from the list of connected serials. if(!activeDevice.isDuplicate()) connectedSerials.remove(activeDevice.getSerial()); } else if(deviceStatus.equals(SmsModemStatus.CONNECTING)) { // The max speed for this connection has been found. If this connection // is a duplicate, we should set the duplicate flag to true. Otherwise, // we may wish to reconnect. if (autoConnectToNewPhones) { boolean isDuplicate = !connectedSerials.add(activeDevice.getSerial()); activeDevice.setDuplicate(isDuplicate); if(!isDuplicate) activeDevice.connect(); } } if (isFailedStatus(deviceStatus)) { if(this.eventBus != null) { NoSmsServicesConnectedNotification notification = createNoSmsDevicesConnectedNotification(); if(notification != null) { this.eventBus.notifyObservers(notification); } } } } if (smsListener != null) { smsListener.smsDeviceEvent(device, deviceStatus); } LOG.trace("EXIT"); } /** * Creates a {@link NoSmsServicesConnectedNotification} based on the current status of attached. If any devices * are connected or still processing, a notification is not created. * {@link SmsService}s. * @return a {@link NoSmsServicesConnectedNotification} describing the current lack of connected devices, or <code>null</code> if there are devices connected or in the process of connecting. */ private NoSmsServicesConnectedNotification createNoSmsDevicesConnectedNotification() { // Check if all other devices have finished detecting. If that's the case, and no // devices have been detected, we throw a NoSmsDevicesDetectedNotification. boolean incompatibleDevicesDetected = false; boolean ownedPortsDetected = false; boolean deviceDetectedOrDetectionInProgress = false; checkAll:for (SmsService device : getAll()) { if(device instanceof SmsModem) { SmsModemStatus status = ((SmsModem)device).getStatus(); switch(status) { case FAILED_TO_CONNECT: case GSM_REG_FAILED: case DISCONNECTED: incompatibleDevicesDetected = true; break; case OWNED_BY_SOMEONE_ELSE: ownedPortsDetected = true; break; case CONNECTED: case CONNECTING: case DETECTED: case DISCONNECTING: case DORMANT: case MAX_SPEED_FOUND: case SEARCHING: case TRY_TO_CONNECT: deviceDetectedOrDetectionInProgress = true; break checkAll; case DISCONNECT_FORCED: case NO_PHONE_DETECTED: case DUPLICATE: // ignore this break; } } else if(device instanceof SmsInternetService) { switch(((SmsInternetService)device).getStatus()) { case CONNECTED: case CONNECTING: case DORMANT: case LOW_CREDIT: case TRYING_TO_RECONNECT: deviceDetectedOrDetectionInProgress = true; break checkAll; case RECEIVING_FAILED: // ignore this as it's not really relevant here break; case DISCONNECTED: case FAILED_TO_CONNECT: // ignore this - we only prompt to help connect phones, not internet services break; } } } if(deviceDetectedOrDetectionInProgress) { return null; } else { return new NoSmsServicesConnectedNotification(incompatibleDevicesDetected, ownedPortsDetected); } } /** * Check if the given {@link SmsServiceStatus} belongs to the failed statuses which should show the device connection problem dialog * @param deviceStatus * @return <code>true</code> if the {@link SmsService} is a failed status, <code>false</code> otherwise */ private static boolean isFailedStatus(SmsServiceStatus deviceStatus) { return deviceStatus.equals(SmsModemStatus.OWNED_BY_SOMEONE_ELSE) || deviceStatus.equals(SmsModemStatus.NO_PHONE_DETECTED) || deviceStatus.equals(SmsModemStatus.GSM_REG_FAILED) || deviceStatus.equals(SmsModemStatus.FAILED_TO_CONNECT); } /** * Get's all {@link SmsService}s that this manager is currently connected to * or investigating. * @return */ public Collection<SmsService> getAll() { Set<SmsService> ret = new HashSet<SmsService>(); ret.addAll(phoneHandlers.values()); ret.addAll(smsInternetServices); return ret; } /** * Request the phone manager to attempt a connection to a particular COM port. * @param port */ public boolean requestConnect(String port) throws NoSuchPortException { return requestConnect(CommPortIdentifier.getPortIdentifier(port), null, true); } /** * Request the phone manager to attempt a connection to a particular COM port. * @param port * @param simPin the PIN to use when connecting to the phone */ public boolean requestConnect(String port, String simPin) throws NoSuchPortException { return requestConnect(CommPortIdentifier.getPortIdentifier(port), simPin, true); } /** * <p>Attempt to connect to an {@link SmsModem} on a particular COM port. This method allows you to specify * the preffered {@link CATHandler} to be used.</p> * <p>If the port is already in use then connection to the port will not be attempted.</p> * @param portName * @param baudRate * @param preferredCATHandler * @return <code>true</code> if connection is being attempted to the port; <code>false</code> if the port is already in use. * @throws NoSuchPortException */ public boolean requestConnect(String portName, String simPin, int baudRate, String preferredCATHandler) throws NoSuchPortException { CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(portName); if(LOG.isInfoEnabled()) LOG.info("Requested connection to port: '" + portName + "'"); if(!portIdentifier.isCurrentlyOwned()) { LOG.info("Connecting to port..."); SmsModem phoneHandler = new SmsModem(portName, this); phoneHandler.setSimPin(simPin); phoneHandlers.put(portName, phoneHandler); phoneHandler.start(baudRate, preferredCATHandler); return true; } else { LOG.info("Port currently owned by another process: '" + portIdentifier.getCurrentOwner() + "'"); // If we don't have a handle on this port, but it's owned by someone else, // then we add it to the phoneHandlers list anyway so that we can see its // status. phoneHandlers.putIfAbsent(portName, new SmsModem(portName, this)); return false; } } public void addSmsInternetService(SmsInternetService smsInternetService) { smsInternetService.setSmsListener(smsListener); if (smsInternetServices.contains(smsInternetService)) { smsInternetService.restartThisThing(); } else { smsInternetServices.add(smsInternetService); smsInternetService.startThisThing(); } } /** * Remove a service from this {@link SmsServiceManager}. * @param service */ public void removeSmsInternetService(SmsInternetService service) { smsInternetServices.remove(service); disconnectSmsInternetService(service); } public void disconnect(SmsService device) { if(device instanceof SmsModem) disconnectPhone((SmsModem)device); else if(device instanceof SmsInternetService) disconnectSmsInternetService((SmsInternetService)device); } private void disconnectPhone(SmsModem modem) { modem.setAutoReconnect(false); handleDisconnect(modem); } public void stopDetection(String port) { SmsModem smsModem = phoneHandlers.get(port); if(smsModem != null) { smsModem.setDetecting(false); smsModem.setAutoReconnect(false); } } private void disconnectSmsInternetService(SmsInternetService device) { device.stopThisThing(); } /** * Attempts to connect to the supplied comm port * @param portIdentifier * @param connectToDiscoveredPhone * @param findPhoneName * @return <code>true</code> if connection is being attempted to the port; <code>false</code> if the port is already in use or does not exist or is not a serial port. */ private boolean requestConnect(CommPortIdentifier portIdentifier, boolean connectToDiscoveredPhones) { return requestConnect(portIdentifier, null, connectToDiscoveredPhones); } /** * Attempts to connect to the supplied comm port * @param portIdentifier * @param connectToDiscoveredPhone * @param findPhoneName * @return <code>true</code> if connection is being attempted to the port; <code>false</code> if the port is already in use or does not exist or is not a serial port. */ private boolean requestConnect(CommPortIdentifier portIdentifier, String simPin, boolean connectToDiscoveredPhones) { String portName = portIdentifier.getName(); LOG.debug("Port Name [" + portName + "]"); if(!shouldIgnore(portName) && portIdentifier.getPortType() == CommPortIdentifier.PORT_SERIAL) { LOG.debug("It is a suitable port."); try { SmsModem modem = new SmsModem(portName, this); modem.setSimPin(simPin); if(!portIdentifier.isCurrentlyOwned()) { LOG.debug("Connecting to port..."); phoneHandlers.put(portName, modem); if(connectToDiscoveredPhones) modem.start(); return true; } else { // If we don't have a handle on this port, but it's owned by someone else, // then we add it to the phoneHandlers list anyway so that we can see its // status. LOG.debug("Port currently owned by another process."); phoneHandlers.putIfAbsent(portName, modem); return false; } } catch(NoSuchPortException ex) { LOG.warn("Port is no longer available.", ex); return false; } } else { // Requesting to connect to a parallel port. Not possible, apparently. // TODO throw a BadPortException or something return false; } } public Collection<SmsInternetService> getSmsInternetServices() { return this.smsInternetServices; } /** * Polls all {@link SmsModem}s that are set to receive messages, and processes any * messages they've received. * THREAD: SmsDeviceManager */ private void processModemReceiving() { Collection<SmsModem> receiveModems = getSmsModemsForReceiving(); for(SmsModem modem : receiveModems) { CIncomingMessage receivedMessage; while((receivedMessage = modem.nextIncomingMessage()) != null) { incomingMessageEvent(modem, receivedMessage); } } } /** @return all {@link SmsModem}s that are currently connected and receiving messages. * THREAD: SmsDeviceManager */ private Collection<SmsModem> getSmsModemsForReceiving() { HashSet<SmsModem> receivers = new HashSet<SmsModem>(); for(SmsModem modem : this.phoneHandlers.values()) { if(modem.isRunning() && modem.isTimedOut()) { // The phone's being unresponsive. Attempt to disconnect from the phone, remove the serial // number from the duplicates list and then add the phone to the reconnect list so we can // reconnect to it later. We should also remove the unresponsive phone from the phoneHandlers // list. if(LOG.isDebugEnabled()) LOG.debug("Watchdog from phone [" + modem.getPort() + "] has timed out! Disconnecting..."); handleDisconnect(modem); } else if(modem.isConnected() && modem.isUseForReceiving()) { receivers.add(modem); } } return receivers; } //> SMS DISPATCH METHODS /** * @param messageType The type of messages which should be dispatched. * The right list is chosen using this type. * THREAD: SmsDeviceManager */ private void dispatchSms(MessageType messageType) { ConcurrentLinkedQueue<FrontlineMessage> outboxFromType = getOutboxFromType(messageType); List<FrontlineMessage> messages = removeAll(outboxFromType); if(messages.size() > 0) { // Try dispatching to SmsInternetServices List<SmsInternetService> internetServices = getSmsInternetServicesForSending(messageType); int serviceCount = internetServices.size(); if(serviceCount > 0) { // We have some SMS Internet services to send with. These are assumed to be higher priority // than Sms Modems, so send all messages with the internet services. dispatchSms(internetServices, messages); } else { // There are no available SMS Internet Services, so dispatch to SmsModems List<SmsModem> sendingModems = getSmsModemsForSending(messageType); if(sendingModems.size() > 0) { dispatchSms(sendingModems, messages); } else { // The messages cannot be sent // We put them back in their outbox outboxFromType.addAll(messages); } } } } /** * * @param messageType The {@link MessageType} * @return The outbox corresponding to the {@link MessageType} */ private ConcurrentLinkedQueue<FrontlineMessage> getOutboxFromType(MessageType messageType) { switch (messageType) { case BINARY: return binOutbox; case UCS2_TEXT: return ucs2Outbox; case GSM7BIT_TEXT: return gsm7bitOutbox; default: throw new IllegalStateException("Unrecognized message type: " + messageType); } } /** * Dispatch some SMS {@link FrontlineMessage}s to some {@link SmsService}s. * @param devices * @param messages * THREAD: SmsDeviceManager */ private void dispatchSms(List<? extends SmsService> devices, List<FrontlineMessage> messages) { int deviceCount = devices.size(); for(FrontlineMessage m : messages) { SmsService device = devices.get(++globalDispatchCounter % deviceCount); // Presumably the device will complain somehow if it is no longer connected // etc. TODO we should actually check what happens! device.sendSMS(m); outgoingMessageEvent(device, m); } } /** Removes and returns all messages currently available in a list. */ private List<FrontlineMessage> removeAll(ConcurrentLinkedQueue<FrontlineMessage> outbox) { LinkedList<FrontlineMessage> retrieved = new LinkedList<FrontlineMessage>(); FrontlineMessage m; while((m=outbox.poll())!=null) retrieved.add(m); return retrieved; } /** @return all {@link SmsInternetService} which are available for sending messages. */ private List<SmsInternetService> getSmsInternetServicesForSending(MessageType messageType) { ArrayList<SmsInternetService> senders = new ArrayList<SmsInternetService>(); for(SmsInternetService service : this.smsInternetServices) { if(service.isConnected() && service.isUseForSending()) { boolean addService; switch(messageType) { case BINARY: addService = service.isBinarySendingSupported(); break; case UCS2_TEXT: addService = service.isUcs2SendingSupported(); break; case GSM7BIT_TEXT: addService = true; break; default: throw new IllegalStateException(); } if(addService) senders.add(service); } } return senders; } /** @return all {@link SmsModem} which are available for sending messages. */ private List<SmsModem> getSmsModemsForSending(MessageType messageType) { ArrayList<SmsModem> senders = new ArrayList<SmsModem>(); for(SmsModem modem : this.phoneHandlers.values()) { if(modem.isRunning() && modem.isTimedOut()) { // The phone's being unresponsive. Attempt to disconnect from the phone, remove the serial // number from the duplicates list and then add the phone to the reconnect list so we can // reconnect to it later. We should also remove the unresponsive phone from the phoneHandlers // list. if(LOG.isDebugEnabled()) LOG.debug("Watchdog from phone [" + modem.getPort() + "] has timed out! Disconnecting..."); handleDisconnect(modem); } else if(modem.isConnected() && modem.isUseForSending()) { boolean addModem; switch(messageType) { case BINARY: addModem = modem.isBinarySendingSupported(); break; case UCS2_TEXT: addModem = modem.isUcs2SendingSupported(); break; case GSM7BIT_TEXT: addModem = true; break; default: throw new IllegalStateException(); } if(addModem) senders.add(modem); } } return senders; } /** * @return The number of active SMS connections running */ public int getNumberOfActiveConnections() { int total = 0; for(SmsModem modem : this.phoneHandlers.values()) { if (modem.isConnected()) { ++total; } } for (SmsInternetService service : this.smsInternetServices) { if (service.isConnected()) { ++total; } } // NB: this may be cleaner if using FrontlineMessagingServices, // but it doesn't sound really useful right now. return total; } } enum MessageType { GSM7BIT_TEXT, UCS2_TEXT, BINARY; public static MessageType get(FrontlineMessage message) { if(message.isBinaryMessage()) { return BINARY; } else if(GsmAlphabet.areAllCharactersValidGSM(message.getTextContent())) { return GSM7BIT_TEXT; } else { return UCS2_TEXT; } } }