/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.java.sip.communicator.service.notification; import static net.java.sip.communicator.service.notification.NotificationAction.*; import static net.java.sip.communicator.service.notification.event.NotificationActionTypeEvent.ACTION_ADDED; import static net.java.sip.communicator.service.notification.event.NotificationActionTypeEvent.ACTION_CHANGED; import static net.java.sip.communicator.service.notification.event.NotificationActionTypeEvent.ACTION_REMOVED; import static net.java.sip.communicator.service.notification.event.NotificationEventTypeEvent.EVENT_TYPE_ADDED; import static net.java.sip.communicator.service.notification.event.NotificationEventTypeEvent.EVENT_TYPE_REMOVED; import java.util.*; import net.java.sip.communicator.service.notification.event.*; import net.java.sip.communicator.util.*; import org.jitsi.service.configuration.*; /** * The implementation of the <tt>NotificationService</tt>. * * @author Yana Stamcheva * @author Ingo Bauersachs */ class NotificationServiceImpl implements NotificationService { private static final String NOTIFICATIONS_PREFIX = "net.java.sip.communicator.impl.notifications"; /** * Defines the number of actions that have to be registered before cached * notifications are fired. * * Current value = 4 (vibrate action excluded). */ public static final int NUM_ACTIONS = 4; /** * A list of all registered <tt>NotificationChangeListener</tt>s. */ private final List<NotificationChangeListener> changeListeners = new Vector<NotificationChangeListener>(); private final ConfigurationService configService = NotificationServiceActivator.getConfigurationService(); /** * A set of all registered event notifications. */ private final Map<String, Notification> defaultNotifications = new HashMap<String, Notification>(); /** * Contains the notification handler per action type. */ private final Map<String, NotificationHandler> handlers = new HashMap<String, NotificationHandler>(); private final Logger logger = Logger.getLogger(NotificationServiceImpl.class); /** * Queue to cache fired notifications before all handlers are registered. */ private Queue<NotificationData> notificationCache = new LinkedList<NotificationData>(); /** * A set of all registered event notifications. */ private final Map<String, Notification> notifications = new HashMap<String, Notification>(); /** * Creates an instance of <tt>NotificationServiceImpl</tt> by loading all * previously saved notifications. */ NotificationServiceImpl() { // Load all previously saved notifications. this.loadNotifications(); } /** * Adds an object that executes the actual action of a notification action. * If the same action type is added twice, the last added wins. * * @param handler The handler that executes the action. */ public void addActionHandler(NotificationHandler handler) { if(handler == null) throw new IllegalArgumentException("handler cannot be null"); synchronized(handlers) { handlers.put(handler.getActionType(), handler); if((handlers.size() == NUM_ACTIONS) && (notificationCache != null)) { for(NotificationData event : notificationCache) fireNotification(event); notificationCache.clear(); notificationCache = null; } } } /** * Adds the given <tt>listener</tt> to the list of change listeners. * * @param listener the listener that we'd like to register to listen for * changes in the event notifications stored by this service. */ public void addNotificationChangeListener( NotificationChangeListener listener) { synchronized (changeListeners) { changeListeners.add(listener); } } /** * Checking an action when it is edited (property .default=false). * Checking for older versions of the property. If it is older one * we migrate it to new configuration using the default values. * * @param eventType the event type. * @param defaultAction the default action which values we will use. */ private void checkDefaultAgainstLoadedNotification (String eventType, NotificationAction defaultAction) { // checking for new sound action properties if(defaultAction instanceof SoundNotificationAction) { SoundNotificationAction soundDefaultAction = (SoundNotificationAction)defaultAction; SoundNotificationAction soundAction = (SoundNotificationAction) getEventNotificationAction(eventType, ACTION_SOUND); boolean isSoundNotificationEnabledPropExist = getNotificationActionProperty( eventType, defaultAction, "isSoundNotificationEnabled") != null; if(!isSoundNotificationEnabledPropExist) { soundAction.setSoundNotificationEnabled( soundDefaultAction.isSoundNotificationEnabled()); } boolean isSoundPlaybackEnabledPropExist = getNotificationActionProperty( eventType, defaultAction, "isSoundPlaybackEnabled") != null; if(!isSoundPlaybackEnabledPropExist) { soundAction.setSoundPlaybackEnabled( soundDefaultAction.isSoundPlaybackEnabled()); } boolean isSoundPCSpeakerEnabledPropExist = getNotificationActionProperty( eventType, defaultAction, "isSoundPCSpeakerEnabled") != null; if(!isSoundPCSpeakerEnabledPropExist) { soundAction.setSoundPCSpeakerEnabled( soundDefaultAction.isSoundPCSpeakerEnabled()); } boolean fixDialingLoop = false; // hack to fix wrong value:just check whether loop for outgoing call // (dialing) has gone into config as 0, should be -1 if(eventType.equals("Dialing") && soundAction.getLoopInterval() == 0) { soundAction.setLoopInterval( soundDefaultAction.getLoopInterval()); fixDialingLoop = true; } if(!(isSoundNotificationEnabledPropExist && isSoundPCSpeakerEnabledPropExist && isSoundPlaybackEnabledPropExist) || fixDialingLoop) { // this check is done only when the notification // is edited and is not default saveNotification( eventType, soundAction, soundAction.isEnabled(), false); } } } /** * Executes a notification data object on the handlers. * * @param data The notification data to act upon. */ private void fireNotification(NotificationData data) { Notification notification = notifications.get(data.getEventType()); if((notification == null) || !notification.isActive()) return; for(NotificationAction action : notification.getActions().values()) { String actionType = action.getActionType(); if(!action.isEnabled()) continue; NotificationHandler handler = handlers.get(actionType); if (handler == null) continue; try { if(actionType.equals(ACTION_POPUP_MESSAGE)) { ((PopupMessageNotificationHandler) handler).popupMessage( (PopupMessageNotificationAction) action, data.getTitle(), data.getMessage(), data.getIcon(), data.getExtra( NotificationData .POPUP_MESSAGE_HANDLER_TAG_EXTRA)); } else if(actionType.equals(ACTION_LOG_MESSAGE)) { ((LogMessageNotificationHandler) handler).logMessage( (LogMessageNotificationAction) action, data.getMessage()); } else if(actionType.equals(ACTION_SOUND)) { SoundNotificationAction soundNotificationAction = (SoundNotificationAction) action; if(soundNotificationAction.isSoundNotificationEnabled() || soundNotificationAction.isSoundPlaybackEnabled() || soundNotificationAction.isSoundPCSpeakerEnabled()) { ((SoundNotificationHandler) handler).start( (SoundNotificationAction) action, data); } } else if(actionType.equals(ACTION_COMMAND)) { @SuppressWarnings("unchecked") Map<String, String> cmdargs = (Map<String, String>) data.getExtra( NotificationData .COMMAND_NOTIFICATION_HANDLER_CMDARGS_EXTRA); ((CommandNotificationHandler) handler).execute( (CommandNotificationAction) action, cmdargs); } else if(actionType.equals(ACTION_VIBRATE)) { ((VibrateNotificationHandler) handler).vibrate( (VibrateNotificationAction) action); } } catch(Exception e) { logger.error("Error dispatching notification of type" + actionType + " from " + handler, e); } } } /** * If there is a registered event notification of the given * <tt>eventType</tt> and the event notification is currently activated, we * go through the list of registered actions and execute them. * * @param eventType the type of the event that we'd like to fire a * notification for. * @return An object referencing the notification. It may be used to stop a * still running notification. Can be null if the eventType is unknown or * the notification is not active. */ public NotificationData fireNotification(String eventType) { return fireNotification(eventType, null, null, null); } /** * If there is a registered event notification of the given * <tt>eventType</tt> and the event notification is currently activated, the * list of registered actions is executed. * * @param eventType the type of the event that we'd like to fire a * notification for. * @param title the title of the given message * @param message the message to use if and where appropriate (e.g. with * systray or log notification.) * @param icon the icon to show in the notification if and where appropriate * @return An object referencing the notification. It may be used to stop a * still running notification. Can be null if the eventType is unknown or * the notification is not active. */ public NotificationData fireNotification( String eventType, String title, String message, byte[] icon) { return fireNotification(eventType, title, message, icon, null); } /** * If there is a registered event notification of the given * <tt>eventType</tt> and the event notification is currently activated, the * list of registered actions is executed. * * @param eventType the type of the event that we'd like to fire a * notification for. * @param title the title of the given message * @param message the message to use if and where appropriate (e.g. with * systray or log notification.) * @param icon the icon to show in the notification if and where appropriate * @param extras additiona/extra {@link NotificationHandler}-specific data * to be provided to the firing of the specified notification(s). The * well-known keys are defined by the <tt>NotificationData</tt> * <tt>XXX_EXTRA</tt> constants. * @return An object referencing the notification. It may be used to stop a * still running notification. Can be null if the eventType is unknown or * the notification is not active. */ public NotificationData fireNotification( String eventType, String title, String message, byte[] icon, Map<String,Object> extras) { Notification notification = notifications.get(eventType); if ((notification == null) || !notification.isActive()) return null; NotificationData data = new NotificationData(eventType, title, message, icon, extras); //cache the notification when the handlers are not yet ready if (notificationCache != null) notificationCache.add(data); else fireNotification(data); return data; } /** * Notifies all registered <tt>NotificationChangeListener</tt>s that a * <tt>NotificationActionTypeEvent</tt> has occurred. * * @param eventType the type of the event, which is one of ACTION_XXX * constants declared in the <tt>NotificationActionTypeEvent</tt> class. * @param sourceEventType the <tt>eventType</tt>, which is the parent of the * action * @param action the notification action */ private void fireNotificationActionTypeEvent( String eventType, String sourceEventType, NotificationAction action) { NotificationActionTypeEvent event = new NotificationActionTypeEvent( this, eventType, sourceEventType, action); for(NotificationChangeListener listener : changeListeners) { if (eventType.equals(ACTION_ADDED)) { listener.actionAdded(event); } else if (eventType.equals(ACTION_REMOVED)) { listener.actionRemoved(event); } else if (eventType.equals(ACTION_CHANGED)) { listener.actionChanged(event); } } } /** * Notifies all registered <tt>NotificationChangeListener</tt>s that a * <tt>NotificationEventTypeEvent</tt> has occurred. * * @param eventType the type of the event, which is one of EVENT_TYPE_XXX * constants declared in the <tt>NotificationEventTypeEvent</tt> class. * @param sourceEventType the <tt>eventType</tt>, for which this event is * about */ private void fireNotificationEventTypeEvent(String eventType, String sourceEventType) { if (logger.isDebugEnabled()) logger.debug("Dispatching NotificationEventType Change. Listeners=" + changeListeners.size() + " evt=" + eventType); NotificationEventTypeEvent event = new NotificationEventTypeEvent(this, eventType, sourceEventType); for (NotificationChangeListener listener : changeListeners) { if (eventType.equals(EVENT_TYPE_ADDED)) { listener.eventTypeAdded(event); } else if (eventType.equals(EVENT_TYPE_REMOVED)) { listener.eventTypeRemoved(event); } } } /** * Gets a list of handler for the specified action type. * * @param actionType the type for which the list of handlers should be * retrieved or <tt>null</tt> if all handlers shall be returned. */ public Iterable<NotificationHandler> getActionHandlers(String actionType) { if (actionType != null) { NotificationHandler handler = handlers.get(actionType); Set<NotificationHandler> ret; if (handler == null) ret = Collections.emptySet(); else ret = Collections.singleton(handler); return ret; } else return handlers.values(); } /** * Returns the notification action corresponding to the given * <tt>eventType</tt> and <tt>actionType</tt>. * * @param eventType the type of the event that we'd like to retrieve. * @param actionType the type of the action that we'd like to retrieve a * descriptor for. * @return the notification action of the action to be executed * when an event of the specified type has occurred. */ public NotificationAction getEventNotificationAction( String eventType, String actionType) { Notification notification = notifications.get(eventType); return (notification == null) ? null : notification.getAction(actionType); } /** * Getting a notification property directly from configuration service. * Used to check do we have an updated version of already saved/edited * notification configurations. Detects old configurations. * * @param eventType the event type * @param action the action which property to check. * @param property the property name without the action prefix. * @return the property value or null if missing. * @throws IllegalArgumentException when the event ot action is not * found. */ private String getNotificationActionProperty( String eventType, NotificationAction action, String property) throws IllegalArgumentException { String eventTypeNodeName = null; String actionTypeNodeName = null; List<String> eventTypes = configService .getPropertyNamesByPrefix(NOTIFICATIONS_PREFIX, true); for (String eventTypeRootPropName : eventTypes) { String eType = configService.getString(eventTypeRootPropName); if(eType.equals(eventType)) eventTypeNodeName = eventTypeRootPropName; } // If we didn't find the given event type in the configuration // there is not need to further check if(eventTypeNodeName == null) { throw new IllegalArgumentException("Missing event type node"); } // Go through contained actions. String actionPrefix = eventTypeNodeName + ".actions"; List<String> actionTypes = configService .getPropertyNamesByPrefix(actionPrefix, true); for (String actionTypeRootPropName : actionTypes) { String aType = configService.getString(actionTypeRootPropName); if(aType.equals(action.getActionType())) actionTypeNodeName = actionTypeRootPropName; } // If we didn't find the given actionType in the configuration // there is no need to further check if(actionTypeNodeName == null) throw new IllegalArgumentException("Missing action type node"); return (String) configService.getProperty(actionTypeNodeName + "." + property); } /** * Returns an iterator over a list of all events registered in this * notification service. Each line in the returned list consists of * a String, representing the name of the event (as defined by the plugin * that registered it). * * @return an iterator over a list of all events registered in this * notifications service */ public Iterable<String> getRegisteredEvents() { return Collections.unmodifiableSet( notifications.keySet()); } /** * Finds the <tt>EventNotification</tt> corresponding to the given * <tt>eventType</tt> and returns its isActive status. * * @param eventType the name of the event (as defined by the plugin that's * registered it) that we are checking. * @return <code>true</code> if actions for the specified <tt>eventType</tt> * are activated, <code>false</code> - otherwise. If the given * <tt>eventType</tt> is not contained in the list of registered event * types - returns <code>false</code>. */ public boolean isActive(String eventType) { Notification eventNotification = notifications.get(eventType); if(eventNotification == null) return false; return eventNotification.isActive(); } private boolean isDefault(String eventType, String actionType) { List<String> eventTypes = configService .getPropertyNamesByPrefix(NOTIFICATIONS_PREFIX, true); for (String eventTypeRootPropName : eventTypes) { String eType = configService.getString(eventTypeRootPropName); if(!eType.equals(eventType)) continue; List<String> actions = configService .getPropertyNamesByPrefix( eventTypeRootPropName + ".actions", true); for (String actionPropName : actions) { String aType = configService.getString(actionPropName); if(!aType.equals(actionType)) continue; Object isDefaultdObj = configService.getProperty(actionPropName + ".default"); // if setting is missing we accept it is true // this way we override old saved settings if(isDefaultdObj == null) return true; else return Boolean.parseBoolean((String)isDefaultdObj); } } return true; } private boolean isEnabled(String configProperty) { Object isEnabledObj = configService.getProperty(configProperty); // if setting is missing we accept it is true // this way we not affect old saved settings if(isEnabledObj == null) return true; else return Boolean.parseBoolean((String)isEnabledObj); } /** * Loads all previously saved event notifications. */ private void loadNotifications() { List<String> eventTypes = configService .getPropertyNamesByPrefix(NOTIFICATIONS_PREFIX, true); for (String eventTypeRootPropName : eventTypes) { boolean isEventActive = isEnabled(eventTypeRootPropName + ".active"); String eventType = configService.getString(eventTypeRootPropName); List<String> actions = configService .getPropertyNamesByPrefix( eventTypeRootPropName + ".actions", true); for (String actionPropName : actions) { String actionType = configService.getString(actionPropName); NotificationAction action = null; if(actionType.equals(ACTION_SOUND)) { String soundFileDescriptor = configService.getString( actionPropName + ".soundFileDescriptor"); String loopInterval = configService.getString( actionPropName + ".loopInterval"); boolean isSoundNotificationEnabled = configService.getBoolean( actionPropName + ".isSoundNotificationEnabled", (soundFileDescriptor != null)); boolean isSoundPlaybackEnabled = configService.getBoolean( actionPropName + ".isSoundPlaybackEnabled", false); boolean isSoundPCSpeakerEnabled = configService.getBoolean( actionPropName + ".isSoundPCSpeakerEnabled", false); action = new SoundNotificationAction( soundFileDescriptor, Integer.parseInt(loopInterval), isSoundNotificationEnabled, isSoundPlaybackEnabled, isSoundPCSpeakerEnabled); } else if(actionType.equals(ACTION_POPUP_MESSAGE)) { String defaultMessage = configService.getString( actionPropName + ".defaultMessage"); long timeout = configService.getLong( actionPropName + ".timeout",-1); String groupName = configService.getString( actionPropName + ".groupName"); action = new PopupMessageNotificationAction( defaultMessage, timeout, groupName); } else if(actionType.equals(ACTION_LOG_MESSAGE)) { String logType = configService.getString( actionPropName + ".logType"); action = new LogMessageNotificationAction(logType); } else if(actionType.equals(ACTION_COMMAND)) { String commandDescriptor = configService.getString( actionPropName + ".commandDescriptor"); action = new CommandNotificationAction(commandDescriptor); } else if(actionType.equals(ACTION_VIBRATE)) { String descriptor = configService.getString( actionPropName + ".descriptor"); int patternLen = configService.getInt( actionPropName + ".patternLength", -1); if(patternLen == -1) { logger.error("Invalid pattern length: "+patternLen); continue; } long[] pattern = new long[patternLen]; for(int pIdx=0; pIdx<patternLen; pIdx++) { pattern[pIdx] = configService.getLong( actionPropName+".patternItem"+pIdx, -1); if(pattern[pIdx] == -1) { logger.error("Invalid pattern interval: "+pattern); continue; } } int repeat = configService.getInt( actionPropName + ".repeat", -1); action = new VibrateNotificationAction( descriptor, pattern, repeat); } action.setEnabled(isEnabled(actionPropName + ".enabled")); // Load the data in the notifications table. Notification notification = notifications.get(eventType); if(notification == null) { notification = new Notification(eventType); notifications.put(eventType, notification); } notification.setActive(isEventActive); notification.addAction(action); } } } /** * Creates a new default <tt>EventNotification</tt> or obtains the * corresponding existing one and registers a new action in it. * * @param eventType the name of the event (as defined by the plugin that's * registering it) that we are setting an action for. * @param action the <tt>NotificationAction</tt> to register */ public void registerDefaultNotificationForEvent( String eventType, NotificationAction action) { if(isDefault(eventType, action.getActionType())) { NotificationAction h = getEventNotificationAction(eventType, action.getActionType()); boolean isNew = false; if(h == null) { isNew = true; h = action; } this.saveNotification( eventType, action, h.isEnabled(), true); Notification notification = null; if(notifications.containsKey(eventType)) notification = notifications.get(eventType); else { notification = new Notification(eventType); notifications.put(eventType, notification); } notification.addAction(action); // We fire the appropriate event depending on whether this is an // already existing actionType or a new one. fireNotificationActionTypeEvent( isNew ? ACTION_ADDED : ACTION_CHANGED, eventType, action); } else checkDefaultAgainstLoadedNotification(eventType, action); // now store this default events if we want to restore them Notification notification = null; if(defaultNotifications.containsKey(eventType)) notification = defaultNotifications.get(eventType); else { notification = new Notification(eventType); defaultNotifications.put(eventType, notification); } notification.addAction(action); } /** * Creates a new default <tt>EventNotification</tt> or obtains the corresponding * existing one and registers a new action in it. * * @param eventType the name of the event (as defined by the plugin that's * registering it) that we are setting an action for. * @param actionType the type of the action that is to be executed when the * specified event occurs (could be one of the ACTION_XXX fields). * @param actionDescriptor a String containing a description of the action * (a URI to the sound file for audio notifications or a command line for * exec action types) that should be executed when the action occurs. * @param defaultMessage the default message to use if no specific message * has been provided when firing the notification. */ public void registerDefaultNotificationForEvent( String eventType, String actionType, String actionDescriptor, String defaultMessage) { if (logger.isDebugEnabled()) logger.debug("Registering default event " + eventType + "/" + actionType + "/" + actionDescriptor + "/" + defaultMessage); if(isDefault(eventType, actionType)) { NotificationAction action = getEventNotificationAction(eventType, actionType); boolean isNew = false; if(action == null) { isNew = true; if (actionType.equals(ACTION_SOUND)) { action = new SoundNotificationAction(actionDescriptor, -1); } else if (actionType.equals(ACTION_LOG_MESSAGE)) { action = new LogMessageNotificationAction( LogMessageNotificationAction.INFO_LOG_TYPE); } else if (actionType.equals(ACTION_POPUP_MESSAGE)) { action = new PopupMessageNotificationAction(defaultMessage); } else if (actionType.equals(ACTION_COMMAND)) { action = new CommandNotificationAction(actionDescriptor); } } this.saveNotification( eventType, action, action.isEnabled(), true); Notification notification = null; if(notifications.containsKey(eventType)) notification = notifications.get(eventType); else { notification = new Notification(eventType); notifications.put(eventType, notification); } notification.addAction(action); // We fire the appropriate event depending on whether this is an // already existing actionType or a new one. fireNotificationActionTypeEvent( isNew ? ACTION_ADDED : ACTION_CHANGED, eventType, action); } // now store this default events if we want to restore them Notification notification = null; if(defaultNotifications.containsKey(eventType)) notification = defaultNotifications.get(eventType); else { notification = new Notification(eventType); defaultNotifications.put(eventType, notification); } NotificationAction action = null; if (actionType.equals(ACTION_SOUND)) { action = new SoundNotificationAction(actionDescriptor, -1); } else if (actionType.equals(ACTION_LOG_MESSAGE)) { action = new LogMessageNotificationAction( LogMessageNotificationAction.INFO_LOG_TYPE); } else if (actionType.equals(ACTION_POPUP_MESSAGE)) { action = new PopupMessageNotificationAction(defaultMessage); } else if (actionType.equals(ACTION_COMMAND)) { action = new CommandNotificationAction(actionDescriptor); } notification.addAction(action); } /** * Creates a new <tt>EventNotification</tt> or obtains the corresponding * existing one and registers a new action in it. * * @param eventType the name of the event (as defined by the plugin that's * registering it) that we are setting an action for. * @param action the <tt>NotificationAction</tt> responsible for * handling the given <tt>actionType</tt> */ public void registerNotificationForEvent( String eventType, NotificationAction action) { Notification notification = null; if(notifications.containsKey(eventType)) notification = notifications.get(eventType); else { notification = new Notification(eventType); notifications.put(eventType, notification); this.fireNotificationEventTypeEvent( EVENT_TYPE_ADDED, eventType); } Object existingAction = notification.addAction(action); // We fire the appropriate event depending on whether this is an // already existing actionType or a new one. if (existingAction != null) { fireNotificationActionTypeEvent( ACTION_CHANGED, eventType, action); } else { fireNotificationActionTypeEvent( ACTION_ADDED, eventType, action); } // Save the notification through the ConfigurationService. this.saveNotification(eventType, action, true, false); } /** * Creates a new <tt>EventNotification</tt> or obtains the corresponding * existing one and registers a new action in it. * * @param eventType the name of the event (as defined by the plugin that's * registering it) that we are setting an action for. * @param actionType the type of the action that is to be executed when the * specified event occurs (could be one of the ACTION_XXX fields). * @param actionDescriptor a String containing a description of the action * (a URI to the sound file for audio notifications or a command line for * exec action types) that should be executed when the action occurs. * @param defaultMessage the default message to use if no specific message * has been provided when firing the notification. */ public void registerNotificationForEvent( String eventType, String actionType, String actionDescriptor, String defaultMessage) { if (logger.isDebugEnabled()) logger.debug("Registering event " + eventType + "/" + actionType + "/" + actionDescriptor + "/" + defaultMessage); if (actionType.equals(ACTION_SOUND)) { Notification notification = defaultNotifications.get(eventType); SoundNotificationAction action = (SoundNotificationAction) notification.getAction(ACTION_SOUND); registerNotificationForEvent ( eventType, new SoundNotificationAction( actionDescriptor, action.getLoopInterval())); } else if (actionType.equals(ACTION_LOG_MESSAGE)) { registerNotificationForEvent (eventType, new LogMessageNotificationAction( LogMessageNotificationAction.INFO_LOG_TYPE)); } else if (actionType.equals(ACTION_POPUP_MESSAGE)) { registerNotificationForEvent (eventType, new PopupMessageNotificationAction(defaultMessage)); } else if (actionType.equals(ACTION_COMMAND)) { registerNotificationForEvent (eventType, new CommandNotificationAction(actionDescriptor)); } } /** * Removes an object that executes the actual action of notification action. * @param actionType The handler type to remove. */ public void removeActionHandler(String actionType) { if(actionType == null) throw new IllegalArgumentException("actionType cannot be null"); synchronized(handlers) { handlers.remove(actionType); } } /** * Removes the <tt>EventNotification</tt> corresponding to the given * <tt>eventType</tt> from the table of registered event notifications. * * @param eventType the name of the event (as defined by the plugin that's * registering it) to be removed. */ public void removeEventNotification(String eventType) { notifications.remove(eventType); this.fireNotificationEventTypeEvent( EVENT_TYPE_REMOVED, eventType); } /** * Removes the given actionType from the list of actions registered for the * given <tt>eventType</tt>. * * @param eventType the name of the event (as defined by the plugin that's * registering it) for which we'll remove the notification. * @param actionType the type of the action that is to be executed when the * specified event occurs (could be one of the ACTION_XXX fields). */ public void removeEventNotificationAction( String eventType, String actionType) { Notification notification = notifications.get(eventType); if(notification == null) return; NotificationAction action = notification.getAction(actionType); if(action == null) return; notification.removeAction(actionType); saveNotification( eventType, action, false, false); fireNotificationActionTypeEvent( ACTION_REMOVED, eventType, action); } /** * Removes the given <tt>listener</tt> from the list of change listeners. * * @param listener the listener that we'd like to remove */ public void removeNotificationChangeListener( NotificationChangeListener listener) { synchronized (changeListeners) { changeListeners.remove(listener); } } /** * Deletes all registered events and actions * and registers and saves the default events as current. */ public void restoreDefaults() { for (String eventType : new Vector<String>(notifications.keySet())) { Notification notification = notifications.get(eventType); for (String actionType : new Vector<String>(notification.getActions().keySet())) removeEventNotificationAction(eventType, actionType); removeEventNotification(eventType); } for (Map.Entry<String, Notification> entry : defaultNotifications.entrySet()) { String eventType = entry.getKey(); Notification notification = entry.getValue(); for (NotificationAction action : notification.getActions().values()) registerNotificationForEvent(eventType, action); } } /** * Saves the event notification given by these parameters through the * <tt>ConfigurationService</tt>. * * @param eventType the name of the event * @param action the notification action to change * @param isActive is the event active * @param isDefault is it a default one */ private void saveNotification( String eventType, NotificationAction action, boolean isActive, boolean isDefault) { String eventTypeNodeName = null; String actionTypeNodeName = null; List<String> eventTypes = configService .getPropertyNamesByPrefix(NOTIFICATIONS_PREFIX, true); for (String eventTypeRootPropName : eventTypes) { String eType = configService.getString(eventTypeRootPropName); if(eType.equals(eventType)) eventTypeNodeName = eventTypeRootPropName; } // If we didn't find the given event type in the configuration we save // it here. if(eventTypeNodeName == null) { eventTypeNodeName = NOTIFICATIONS_PREFIX + ".eventType" + Long.toString(System.currentTimeMillis()); configService.setProperty(eventTypeNodeName, eventType); } // if we set active/inactive for the whole event notification if(action == null) { configService.setProperty( eventTypeNodeName + ".active", Boolean.toString(isActive)); return; } // Go through contained actions. String actionPrefix = eventTypeNodeName + ".actions"; List<String> actionTypes = configService .getPropertyNamesByPrefix(actionPrefix, true); for (String actionTypeRootPropName : actionTypes) { String aType = configService.getString(actionTypeRootPropName); if(aType.equals(action.getActionType())) actionTypeNodeName = actionTypeRootPropName; } Map<String, Object> configProperties = new HashMap<String, Object>(); // If we didn't find the given actionType in the configuration we save // it here. if(actionTypeNodeName == null) { actionTypeNodeName = actionPrefix + ".actionType" + Long.toString(System.currentTimeMillis()); configProperties.put(actionTypeNodeName, action.getActionType()); } if(action instanceof SoundNotificationAction) { SoundNotificationAction soundAction = (SoundNotificationAction) action; configProperties.put( actionTypeNodeName + ".soundFileDescriptor", soundAction.getDescriptor()); configProperties.put( actionTypeNodeName + ".loopInterval", soundAction.getLoopInterval()); configProperties.put( actionTypeNodeName + ".isSoundNotificationEnabled", soundAction.isSoundNotificationEnabled()); configProperties.put( actionTypeNodeName + ".isSoundPlaybackEnabled", soundAction.isSoundPlaybackEnabled()); configProperties.put( actionTypeNodeName + ".isSoundPCSpeakerEnabled", soundAction.isSoundPCSpeakerEnabled()); } else if(action instanceof PopupMessageNotificationAction) { PopupMessageNotificationAction messageAction = (PopupMessageNotificationAction) action; configProperties.put( actionTypeNodeName + ".defaultMessage", messageAction.getDefaultMessage()); configProperties.put( actionTypeNodeName + ".timeout", messageAction.getTimeout()); configProperties.put( actionTypeNodeName + ".groupName", messageAction.getGroupName()); } else if(action instanceof LogMessageNotificationAction) { LogMessageNotificationAction logMessageAction = (LogMessageNotificationAction) action; configProperties.put( actionTypeNodeName + ".logType", logMessageAction.getLogType()); } else if(action instanceof CommandNotificationAction) { CommandNotificationAction commandAction = (CommandNotificationAction) action; configProperties.put( actionTypeNodeName + ".commandDescriptor", commandAction.getDescriptor()); } else if(action instanceof VibrateNotificationAction) { VibrateNotificationAction vibrateAction = (VibrateNotificationAction) action; configProperties.put(actionTypeNodeName + ".descriptor", vibrateAction.getDescriptor()); long[] pattern = vibrateAction.getPattern(); configProperties.put( actionTypeNodeName + ".patternLength", pattern.length); for(int pIdx = 0; pIdx < pattern.length; pIdx++) { configProperties.put( actionTypeNodeName + ".patternItem" + pIdx, pattern[pIdx]); } configProperties.put(actionTypeNodeName + ".repeat", vibrateAction.getRepeat()); } configProperties.put( actionTypeNodeName + ".enabled", Boolean.toString(isActive)); configProperties.put( actionTypeNodeName + ".default", Boolean.toString(isDefault)); configService.setProperties(configProperties); } /** * Finds the <tt>EventNotification</tt> corresponding to the given * <tt>eventType</tt> and marks it as activated/deactivated. * * @param eventType the name of the event, which actions should be activated * /deactivated. * @param isActive indicates whether to activate or deactivate the actions * related to the specified <tt>eventType</tt>. */ public void setActive(String eventType, boolean isActive) { Notification eventNotification = notifications.get(eventType); if(eventNotification == null) return; eventNotification.setActive(isActive); saveNotification(eventType, null, isActive, false); } /** * Stops a notification if notification is continuous, like playing sounds * in loop. Do nothing if there are no such events currently processing. * * @param data the data that has been returned when firing the event.. */ public void stopNotification(NotificationData data) { Iterable<NotificationHandler> soundHandlers = getActionHandlers(NotificationAction.ACTION_SOUND); // There could be no sound action handler for this event type if (soundHandlers != null) { for (NotificationHandler handler : soundHandlers) { if (handler instanceof SoundNotificationHandler) ((SoundNotificationHandler) handler).stop(data); } } Iterable<NotificationHandler> vibrateHandlers = getActionHandlers(NotificationAction.ACTION_VIBRATE); if(vibrateHandlers != null) { for(NotificationHandler handler : vibrateHandlers) { ((VibrateNotificationHandler)handler).cancel(); } } } /** * Tells if the given sound notification is currently played. * * @param data Additional data for the event. */ public boolean isPlayingNotification(NotificationData data) { boolean isPlaying = false; Iterable<NotificationHandler> soundHandlers = getActionHandlers(NotificationAction.ACTION_SOUND); // There could be no sound action handler for this event type if (soundHandlers != null) { for (NotificationHandler handler : soundHandlers) { if (handler instanceof SoundNotificationHandler) { isPlaying |= ((SoundNotificationHandler) handler).isPlaying(data); } } } return isPlaying; } }