diff --git a/lib/installer-exclude/libjitsi.jar b/lib/installer-exclude/libjitsi.jar index c1091d48e..6feb19ad8 100644 Binary files a/lib/installer-exclude/libjitsi.jar and b/lib/installer-exclude/libjitsi.jar differ diff --git a/src/net/java/sip/communicator/impl/neomedia/NeomediaActivator.java b/src/net/java/sip/communicator/impl/neomedia/NeomediaActivator.java index edf966b24..bf8b5b6b7 100644 --- a/src/net/java/sip/communicator/impl/neomedia/NeomediaActivator.java +++ b/src/net/java/sip/communicator/impl/neomedia/NeomediaActivator.java @@ -6,31 +6,23 @@ */ package net.java.sip.communicator.impl.neomedia; -import java.awt.*; -import java.awt.event.*; import java.beans.*; import java.util.*; -import javax.swing.*; - import net.java.sip.communicator.impl.neomedia.codec.video.h264.*; import net.java.sip.communicator.service.gui.*; import net.java.sip.communicator.service.notification.*; import net.java.sip.communicator.service.resources.*; -import net.java.sip.communicator.service.systray.*; import net.java.sip.communicator.service.systray.event.*; import net.java.sip.communicator.util.*; -import net.java.sip.communicator.util.swing.*; import org.jitsi.impl.neomedia.*; -import org.jitsi.impl.neomedia.codec.*; import org.jitsi.impl.neomedia.device.*; import org.jitsi.service.audionotifier.*; import org.jitsi.service.configuration.*; import org.jitsi.service.fileaccess.*; import org.jitsi.service.libjitsi.*; import org.jitsi.service.neomedia.*; -import org.jitsi.service.neomedia.codec.*; import org.jitsi.service.packetlogging.*; import org.jitsi.service.resources.*; import org.osgi.framework.*; @@ -146,11 +138,6 @@ public class NeomediaActivator private AudioDeviceConfigurationListener deviceConfigurationPropertyChangeListener; - /** - * Audio configuration dialog. - */ - private static SIPCommDialog audioConfigDialog = null; - /** * A {@link MediaConfigurationService} instance. */ @@ -553,7 +540,7 @@ public void managePopupMessageListenerRegistration(boolean enable) } /** - * Fonction called when an audio device is plugged or unplugged. + * Function called when an audio device is plugged or unplugged. * * @param The property change event which may concern the audio device. */ @@ -564,6 +551,7 @@ public void propertyChange(PropertyChangeEvent event) { NotificationService notificationService = getNotificationService(); + if(notificationService != null) { // Registers only once to the popup message notification @@ -573,9 +561,15 @@ public void propertyChange(PropertyChangeEvent event) isRegisteredToPopupMessageListener = true; managePopupMessageListenerRegistration(true); } + // Fires the popup notification. ResourceManagementService resources = NeomediaActivator.getResources(); + Map extras = new HashMap(); + + extras.put( + NotificationData.POPUP_MESSAGE_HANDLER_TAG_EXTRA, + this); notificationService.fireNotification( DEVICE_CONFIGURATION_HAS_CHANGED, resources.getI18NString( @@ -585,7 +579,7 @@ public void propertyChange(PropertyChangeEvent event) "impl.media.configform" + ".AUDIO_DEVICE_CONFIG_MANAGMENT_CLICK"), null, - this); + extras); } } } diff --git a/src/net/java/sip/communicator/impl/notification/PopupMessageNotificationHandlerImpl.java b/src/net/java/sip/communicator/impl/notification/PopupMessageNotificationHandlerImpl.java index 339503240..c15fb538c 100644 --- a/src/net/java/sip/communicator/impl/notification/PopupMessageNotificationHandlerImpl.java +++ b/src/net/java/sip/communicator/impl/notification/PopupMessageNotificationHandlerImpl.java @@ -57,11 +57,14 @@ public void popupMessage(PopupMessageNotificationAction action, return; if(!StringUtils.isNullOrEmpty(message)) + { systray.showPopupMessage( - new PopupMessage(title, message, icon, tag)); + new PopupMessage(title, message, icon, tag)); + } else - logger.error("Message is null or empty!", - new Throwable("Null or empty message")); + { + logger.error("Message is null or empty!"); + } } /** diff --git a/src/net/java/sip/communicator/impl/notification/SoundNotificationHandlerImpl.java b/src/net/java/sip/communicator/impl/notification/SoundNotificationHandlerImpl.java index fad7e2beb..c3e4ab28e 100644 --- a/src/net/java/sip/communicator/impl/notification/SoundNotificationHandlerImpl.java +++ b/src/net/java/sip/communicator/impl/notification/SoundNotificationHandlerImpl.java @@ -8,6 +8,7 @@ import java.awt.*; import java.util.*; +import java.util.concurrent.*; import net.java.sip.communicator.service.gui.*; import net.java.sip.communicator.service.notification.*; @@ -23,13 +24,15 @@ public class SoundNotificationHandlerImpl implements SoundNotificationHandler { - WeakHashMap playedClips = - new WeakHashMap(); - /** - * If the sound is currently disabled. + * The indicator which determines whether this + * SoundNotificationHandler is currently muted i.e. the sounds are + * off. */ - private boolean isMute; + private boolean mute; + + private Map playedClips + = new WeakHashMap(); /** * {@inheritDoc} @@ -40,55 +43,13 @@ public String getActionType() } /** - * Plays the sound given by the containing soundFileDescriptor. The - * sound is played in loop if the loopInterval is defined. - * @param action The action to act upon. - * @param data Additional data for the event. + * Specifies if currently the sound is off. + * + * @return TRUE if currently the sound is off, FALSE otherwise */ - public void start(SoundNotificationAction action, NotificationData data) + public boolean isMute() { - if(isMute()) - return; - - boolean playOnlyOnPlayback = true; - - AudioNotifierService audioNotifService - = NotificationActivator.getAudioNotifier(); - if(audioNotifService != null) - playOnlyOnPlayback = - audioNotifService.audioOutAndNotificationsShareSameDevice(); - - if(playOnlyOnPlayback) - { - if(action.isSoundNotificationEnabled() - || action.isSoundPlaybackEnabled()) - { - play(action, data, true); - } - } - else - { - if(action.isSoundNotificationEnabled()) - { - play(action, data, false); - } - - if(action.isSoundPlaybackEnabled()) - { - play(action, data, true); - } - } - - if(action.isSoundPCSpeakerEnabled()) - { - PCSpeakerClip audio = new PCSpeakerClip(); - playedClips.put(audio, data); - - if(action.getLoopInterval() > -1) - audio.playInLoop(action.getLoopInterval()); - else - audio.play(); - } + return mute; } /** @@ -96,16 +57,18 @@ public void start(SoundNotificationAction action, NotificationData data) * sound is played in loop if the loopInterval is defined. * @param action The action to act upon. * @param data Additional data for the event. - * @param playback to use or not the playback or notification device. + * @param device */ - private void play(SoundNotificationAction action, NotificationData data, - boolean playback) + private void play( + SoundNotificationAction action, + NotificationData data, + SCAudioClipDevice device) { AudioNotifierService audioNotifService = NotificationActivator.getAudioNotifier(); - if(audioNotifService == null - || StringUtils.isNullOrEmpty(action.getDescriptor(), true)) + if((audioNotifService == null) + || StringUtils.isNullOrEmpty(action.getDescriptor(), true)) return; // this is hack, seen on some os (particularly seen on macosx with @@ -113,51 +76,42 @@ private void play(SoundNotificationAction action, NotificationData data, // when playing notification in the call, can break the call and // no further communicating can be done after the notification. // So we skip playing notification if we have a call running - if(playback) + if(SCAudioClipDevice.PLAYBACK.equals(device)) { UIService uiService = NotificationActivator.getUIService(); + if(uiService.getInProgressCalls().size() > 0) - { return; - } } - SCAudioClip audio = audioNotifService - .createAudio(action.getDescriptor(), playback); + SCAudioClip audio = null; + + switch (device) + { + case NOTIFICATION: + case PLAYBACK: + audio = audioNotifService.createAudio(action.getDescriptor(), SCAudioClipDevice.PLAYBACK.equals(device)); + break; + + case PC_SPEAKER: + audio = new PCSpeakerClip(); + break; + } // it is possible that audio cannot be created if(audio == null) return; playedClips.put(audio, data); - if(action.getLoopInterval() > -1) - audio.playInLoop(action.getLoopInterval()); - else - audio.play(); - } - /** - * Stops the sound. - * @param data Additional data for the event. - */ - public void stop(NotificationData data) - { - AudioNotifierService audioNotifService - = NotificationActivator.getAudioNotifier(); - - if(audioNotifService == null) - return; + @SuppressWarnings("unchecked") + Callable loopCondition + = (Callable) + data.getExtra( + NotificationData + .SOUND_NOTIFICATION_HANDLER_LOOP_CONDITION_EXTRA); - for (Map.Entry entry : playedClips - .entrySet()) - { - if(entry.getValue() == data) - { - SCAudioClip audio = entry.getKey(); - audio.stop(); - audioNotifService.destroyAudio(audio); - } - } + audio.play(action.getLoopInterval(), loopCondition); } /** @@ -167,7 +121,7 @@ public void stop(NotificationData data) */ public void setMute(boolean isMute) { - this.isMute = isMute; + this.mute = isMute; if(isMute) { @@ -189,182 +143,117 @@ public void setMute(boolean isMute) } /** - * Specifies if currently the sound is off. - * - * @return TRUE if currently the sound is off, FALSE otherwise - */ - public boolean isMute() - { - return isMute; - } - - /** - * Plays beep on the pc speaker. + * Plays the sound given by the containing soundFileDescriptor. The + * sound is played in loop if the loopInterval is defined. + * @param action The action to act upon. + * @param data Additional data for the event. */ - private class PCSpeakerClip - implements SCAudioClip + public void start(SoundNotificationAction action, NotificationData data) { - /** - * Synching start/stop. - */ - private final Object syncObject = new Object(); - - /** - * Is beep started. - */ - private boolean started = false; + if(isMute()) + return; - /** - * Is looping. - */ - private boolean isLooping; + boolean playOnlyOnPlayback = true; - /** - * The interval to loop. - */ - private int loopInterval; + AudioNotifierService audioNotifService + = NotificationActivator.getAudioNotifier(); - /** - * Plays this audio. - */ - public void play() + if(audioNotifService != null) { - started = true; - new Thread("Playing beep:" + this.getClass()) - { - @Override - public void run() - { - runInPlayThread(); - } - }.start(); + playOnlyOnPlayback + = audioNotifService.audioOutAndNotificationsShareSameDevice(); } - /** - * Plays this audio in loop. - * - * @param silenceInterval interval between loops - */ - public void playInLoop(int silenceInterval) + if(playOnlyOnPlayback) { - setLoopInterval(silenceInterval); - setIsLooping(true); - - play(); + if(action.isSoundNotificationEnabled() + || action.isSoundPlaybackEnabled()) + { + play(action, data, SCAudioClipDevice.PLAYBACK); + } } - - /** - * Stops this audio. - */ - public void stop() + else { - internalStop(); - setIsLooping(false); + if(action.isSoundNotificationEnabled()) + play(action, data, SCAudioClipDevice.NOTIFICATION); + if(action.isSoundPlaybackEnabled()) + play(action, data, SCAudioClipDevice.PLAYBACK); } - /** - * Stops this audio without setting the isLooping property in the case of - * a looping audio. - */ - public void internalStop() + if(action.isSoundPCSpeakerEnabled()) + play(action, data, SCAudioClipDevice.PC_SPEAKER); + } + + /** + * Stops the sound. + * @param data Additional data for the event. + */ + public void stop(NotificationData data) + { + AudioNotifierService audioNotifService + = NotificationActivator.getAudioNotifier(); + + if(audioNotifService == null) + return; + + for (Map.Entry entry + : playedClips.entrySet()) { - synchronized (syncObject) + if(entry.getValue() == data) { - if (started) - { - started = false; - syncObject.notifyAll(); - } + SCAudioClip audio = entry.getKey(); + + audio.stop(); + audioNotifService.destroyAudio(audio); } } + } + /** + * Beeps the PC speaker. + */ + private static class PCSpeakerClip + extends AbstractSCAudioClip + { /** - * Runs in a separate thread to perform the actual playback. + * Initializes a new PCSpeakerClip instance. */ - private void runInPlayThread() + public PCSpeakerClip() { - while (started) - { - if (!runOnceInPlayThread()) - break; - - if(isLooping()) - { - synchronized(syncObject) - { - if (started) - { - try - { - if(getLoopInterval() > 0) - syncObject.wait(getLoopInterval()); - } - catch (InterruptedException e) - { - } - } - } - } - else - break; - } + super(null, NotificationActivator.getAudioNotifier()); } /** - * Beeps. + * Beeps the PC speaker. * - * @return true if the playback was successful; - * otherwise, false + * @return true if the playback was successful; otherwise, + * false */ - private boolean runOnceInPlayThread() + protected boolean runOnceInPlayThread() { try { Toolkit.getDefaultToolkit().beep(); + return true; } catch (Throwable t) { - //logger.error("Failed to get audio stream " + url, ioex); - return false; + if (t instanceof ThreadDeath) + throw (ThreadDeath) t; + else + return false; } - - return true; - } - - /** - * Returns TRUE if this audio is currently playing in loop, - * FALSE otherwise. - * @return TRUE if this audio is currently playing in loop, - * FALSE otherwise. - */ - public boolean isLooping() - { - return isLooping; - } - - /** - * Returns the loop interval if this audio is looping. - * @return the loop interval if this audio is looping - */ - public int getLoopInterval() - { - return loopInterval; - } - - /** - * @param isLooping the isLooping to set - */ - public void setIsLooping(boolean isLooping) - { - this.isLooping = isLooping; } + } - /** - * @param loopInterval the loopInterval to set - */ - public void setLoopInterval(int loopInterval) - { - this.loopInterval = loopInterval; - } + /** + * Enumerates the types of devices on which SCAudioClips may be + * played back. + */ + private static enum SCAudioClipDevice + { + NOTIFICATION, + PC_SPEAKER, + PLAYBACK } } diff --git a/src/net/java/sip/communicator/plugin/loggingutils/LoggingConfigForm.java b/src/net/java/sip/communicator/plugin/loggingutils/LoggingConfigForm.java index e5db2e6bd..1c1d16201 100644 --- a/src/net/java/sip/communicator/plugin/loggingutils/LoggingConfigForm.java +++ b/src/net/java/sip/communicator/plugin/loggingutils/LoggingConfigForm.java @@ -474,22 +474,19 @@ private void collectLogs() if(notificationService != null) { - String bodyMsgKey = null; - - if(dest != null) - bodyMsgKey = "plugin.loggingutils.ARCHIVE_MESSAGE_OK"; - else - bodyMsgKey = "plugin.loggingutils.ARCHIVE_MESSAGE_NOTOK"; + String bodyMsgKey + = (dest == null) + ? "plugin.loggingutils.ARCHIVE_MESSAGE_NOTOK" + : "plugin.loggingutils.ARCHIVE_MESSAGE_OK"; notificationService.fireNotification( - LOGFILES_ARCHIVED, - resources.getI18NString( - "plugin.loggingutils.ARCHIVE_BUTTON"), - resources.getI18NString( - bodyMsgKey, - new String[]{dest.getAbsolutePath()}), - null, - null); + LOGFILES_ARCHIVED, + resources.getI18NString( + "plugin.loggingutils.ARCHIVE_BUTTON"), + resources.getI18NString( + bodyMsgKey, + new String[]{dest.getAbsolutePath()}), + null); } } @@ -721,20 +718,18 @@ static void uploadLogs( if(notificationService != null) { + ResourceManagementService resources + = LoggingUtilsActivator.getResourceService(); String bodyMsgKey = "plugin.loggingutils.ARCHIVE_MESSAGE_OK"; - ResourceManagementService resources = - LoggingUtilsActivator.getResourceService(); - notificationService.fireNotification( - LOGFILES_ARCHIVED, - resources.getI18NString( - "plugin.loggingutils.ARCHIVE_BUTTON"), - resources.getI18NString( - bodyMsgKey, - new String[]{uploadLocation}), - null, - null); + LOGFILES_ARCHIVED, + resources.getI18NString( + "plugin.loggingutils.ARCHIVE_BUTTON"), + resources.getI18NString( + bodyMsgKey, + new String[]{uploadLocation}), + null); } } } diff --git a/src/net/java/sip/communicator/plugin/notificationwiring/NotificationManager.java b/src/net/java/sip/communicator/plugin/notificationwiring/NotificationManager.java index 4aa9cb158..d2022e399 100644 --- a/src/net/java/sip/communicator/plugin/notificationwiring/NotificationManager.java +++ b/src/net/java/sip/communicator/plugin/notificationwiring/NotificationManager.java @@ -9,6 +9,7 @@ import java.awt.image.*; import java.net.*; import java.util.*; +import java.util.concurrent.*; import javax.imageio.*; @@ -22,72 +23,42 @@ import org.jitsi.service.neomedia.*; import org.jitsi.service.protocol.event.*; +import org.jitsi.service.resources.*; import org.osgi.framework.*; /** - * Listens for all kinds of events and triggers when needed a notification, - * a popup or sound one or other. + * Listens to various events which are related to the display and/or playback of + * notifications and shows/starts or hides/stops the notifications in question. + * * @author Damian Minkov + * @author Lyubomir Marinov */ public class NotificationManager - implements MessageListener, - ServiceListener, - FileTransferListener, - TypingNotificationsListener, - CallListener, + implements AdHocChatRoomMessageListener, CallChangeListener, + CallListener, + CallPeerConferenceListener, CallPeerListener, CallPeerSecurityListener, ChatRoomMessageListener, - LocalUserChatRoomPresenceListener, + FileTransferListener, LocalUserAdHocChatRoomPresenceListener, - AdHocChatRoomMessageListener, - CallPeerConferenceListener, - Recorder.Listener + LocalUserChatRoomPresenceListener, + MessageListener, + Recorder.Listener, + ServiceListener, + TypingNotificationsListener { /** - * Our logger. - */ - private static final Logger logger = - Logger.getLogger(NotificationManager.class); - - /** - * The image used, when a contact has no photo specified. - */ - public static final ImageID DEFAULT_USER_PHOTO - = new ImageID("service.gui.DEFAULT_USER_PHOTO"); - - /** - * Stores all already loaded images. - */ - private static final Map loadedImages = - new Hashtable(); - - /** - * Pseudo timer used to delay multiple typings notifications before - * receiving the message. - * - * Time to live : 1 minute - */ - private Map proactiveTimer = new HashMap(); - - /** - * Stores notification references to stop them if a notification has expired - * (e.g. to stop the dialing sound). + * Default event type for a busy call. */ - private Map callNotifications = - new WeakHashMap(); + public static final String BUSY_CALL = "BusyCall"; /** * Default event type for call been saved using a recorder. */ public static final String CALL_SAVED = "CallSaved"; - /** - * Default event type for incoming file transfers. - */ - public static final String INCOMING_FILE = "IncomingFile"; - /** * Default event type for security error on a call. */ @@ -99,15 +70,15 @@ public class NotificationManager public static final String CALL_SECURITY_ON = "CallSecurityOn"; /** - * Default event type when a secure message received. + * The image used, when a contact has no photo specified. */ - public static final String SECURITY_MESSAGE = "SecurityMessage"; + public static final ImageID DEFAULT_USER_PHOTO + = new ImageID("service.gui.DEFAULT_USER_PHOTO"); /** - * Default event type for - * proactive notifications (typing notifications when chatting). + * Default event type for dialing. */ - public static final String PROACTIVE_NOTIFICATION = "ProactiveNotification"; + public static final String DIALING = "Dialing"; /** * Default event type for hanging up calls. @@ -115,24 +86,22 @@ public class NotificationManager public static final String HANG_UP = "HangUp"; /** - * Default event type for dialing. - */ - public static final String DIALING = "Dialing"; - - /** - * Default event type for a busy call. + * The cache of BufferedImage instances which we have already + * loaded by ImageID and which we store so that we do not have to + * load them again. */ - public static final String BUSY_CALL = "BusyCall"; + private static final Map images + = new Hashtable(); /** - * Default event type for outgoing calls. + * Default event type for receiving calls (incoming calls). */ - public static final String OUTGOING_CALL = "OutgoingCall"; + public static final String INCOMING_CALL = "IncomingCall"; /** - * Default event type for receiving calls (incoming calls). + * Default event type for incoming file transfers. */ - public static final String INCOMING_CALL = "IncomingCall"; + public static final String INCOMING_FILE = "IncomingFile"; /** * Default event type for receiving messages. @@ -140,29 +109,42 @@ public class NotificationManager public static final String INCOMING_MESSAGE = "IncomingMessage"; /** - * Initialize, register default notifications and start listening for - * new protocols or removed one and find any that are already registered. + * The Logger used by the NotificationManager class and + * its instances for logging output. */ - void init() - { - registerDefaultNotifications(); + private static final Logger logger + = Logger.getLogger(NotificationManager.class); - // listens for new protocols - NotificationWiringActivator.bundleContext.addServiceListener(this); + /** + * Default event type for outgoing calls. + */ + public static final String OUTGOING_CALL = "OutgoingCall"; - // enumerate currently registered protocols - for(ProtocolProviderService pp : getProtocolProviders()) - { - handleProviderAdded(pp); - } + /** + * Default event type for + * proactive notifications (typing notifications when chatting). + */ + public static final String PROACTIVE_NOTIFICATION = "ProactiveNotification"; - NotificationWiringActivator.getMediaService().addRecorderListener(this); - } + /** + * Default event type when a secure message received. + */ + public static final String SECURITY_MESSAGE = "SecurityMessage"; /** - * Register all default notifications. + * Fires a chat message notification for the given event type through the + * NotificationService. + * + * @param chatContact the chat contact to which the chat message corresponds; + * the chat contact could be a Contact or a ChatRoom. + * @param eventType the event type for which we fire a notification + * @param messageTitle the title of the message + * @param message the content of the message */ - private void registerDefaultNotifications() + public static void fireChatNotification(Object chatContact, + String eventType, + String messageTitle, + String message) { NotificationService notificationService = NotificationWiringActivator.getNotificationService(); @@ -170,133 +152,249 @@ private void registerDefaultNotifications() if(notificationService == null) return; - // Register incoming message notifications. - notificationService.registerDefaultNotificationForEvent( - INCOMING_MESSAGE, - NotificationAction.ACTION_POPUP_MESSAGE, - null, - null); - - notificationService.registerDefaultNotificationForEvent( - INCOMING_MESSAGE, - new SoundNotificationAction( - SoundProperties.INCOMING_MESSAGE, -1, true, false, false)); - - // Register incoming call notifications. - notificationService.registerDefaultNotificationForEvent( - INCOMING_CALL, - NotificationAction.ACTION_POPUP_MESSAGE, - null, - null); - - SoundNotificationAction inCallSoundHandler - = new SoundNotificationAction( - SoundProperties.INCOMING_CALL, 2000, true, true, true); - - notificationService.registerDefaultNotificationForEvent( - INCOMING_CALL, - inCallSoundHandler); - - // Register outgoing call notifications. - SoundNotificationAction outCallSoundHandler - = new SoundNotificationAction( - SoundProperties.OUTGOING_CALL, 3000, false, true, false); - - notificationService.registerDefaultNotificationForEvent( - OUTGOING_CALL, - outCallSoundHandler); - - // Register busy call notifications. - SoundNotificationAction busyCallSoundHandler - = new SoundNotificationAction(SoundProperties.BUSY, 1, - false, true, false); + NotificationAction popupActionHandler = null; + UIService uiService = NotificationWiringActivator.getUIService(); - notificationService.registerDefaultNotificationForEvent( - BUSY_CALL, - busyCallSoundHandler); + Chat chatPanel = null; + byte[] contactIcon = null; + if (chatContact instanceof Contact) + { + Contact contact = (Contact) chatContact; - // Register dial notifications. - SoundNotificationAction dialSoundHandler - = new SoundNotificationAction( - SoundProperties.DIALING, -1, false, true, false); + if(uiService != null) + chatPanel = uiService.getChat(contact); - notificationService.registerDefaultNotificationForEvent( - DIALING, - dialSoundHandler); + contactIcon = contact.getImage(); + if(contactIcon == null) + { + contactIcon = + ImageUtils.toByteArray(getImage(DEFAULT_USER_PHOTO)); + } + } + else if (chatContact instanceof ChatRoom) + { + ChatRoom chatRoom = (ChatRoom) chatContact; - // Register the hangup sound notification. - SoundNotificationAction hangupSoundHandler - = new SoundNotificationAction( - SoundProperties.HANG_UP, -1, false, true, false); + // For system rooms we don't want to send notification events. + if (chatRoom.isSystem()) + return; - notificationService.registerDefaultNotificationForEvent( - HANG_UP, - hangupSoundHandler); + if(uiService != null) + chatPanel = uiService.getChat(chatRoom); + } - // Register proactive notifications. - notificationService.registerDefaultNotificationForEvent( - PROACTIVE_NOTIFICATION, - NotificationAction.ACTION_POPUP_MESSAGE, - null, - null); + if (chatPanel != null) + { + if (eventType.equals(INCOMING_MESSAGE) + && chatPanel.isChatFocused()) + { + popupActionHandler = notificationService + .getEventNotificationAction(eventType, + NotificationAction.ACTION_POPUP_MESSAGE); - // Register warning message notifications. - notificationService.registerDefaultNotificationForEvent( - SECURITY_MESSAGE, - NotificationAction.ACTION_POPUP_MESSAGE, - null, - null); + popupActionHandler.setEnabled(false); + } + } - // Register sound notification for security state on during a call. - notificationService.registerDefaultNotificationForEvent( - CALL_SECURITY_ON, - new SoundNotificationAction( - SoundProperties.CALL_SECURITY_ON, -1, - false, true, false)); + Map extras = new HashMap(); - // Register sound notification for security state off during a call. - notificationService.registerDefaultNotificationForEvent( - CALL_SECURITY_ERROR, - new SoundNotificationAction( - SoundProperties.CALL_SECURITY_ERROR, -1, - false, true, false)); + extras.put( + NotificationData.POPUP_MESSAGE_HANDLER_TAG_EXTRA, + chatContact); + notificationService.fireNotification( + eventType, + messageTitle, + message, + contactIcon, + extras); - // Register sound notification for incoming files. - notificationService.registerDefaultNotificationForEvent( - INCOMING_FILE, - NotificationAction.ACTION_POPUP_MESSAGE, - null, - null); + if(popupActionHandler != null) + popupActionHandler.setEnabled(true); + } - notificationService.registerDefaultNotificationForEvent( - INCOMING_FILE, - new SoundNotificationAction( - SoundProperties.INCOMING_FILE, -1, - true, false, false)); + /** + * Fires a notification for the given event type through the + * NotificationService. The event type is one of the static + * constants defined in the NotificationManager class. + *

+ * Note: The uses of the method at the time of this writing do not + * take measures to stop looping sounds if the respective notifications use + * them i.e. there is implicit agreement that the notifications fired + * through the method do not loop sounds. Consequently, the method passes + * arguments to NotificationService so that sounds are played once + * only. + *

+ * + * @param eventType the event type for which we want to fire a notification + */ + private static void fireNotification(String eventType) + { + NotificationService notificationService + = NotificationWiringActivator.getNotificationService(); - // Register notification for saved calls. - notificationService.registerDefaultNotificationForEvent( - CALL_SAVED, - NotificationAction.ACTION_POPUP_MESSAGE, - null, - null); + if (notificationService != null) + notificationService.fireNotification(eventType); } /** - * Returns all ProtocolProviderFactorys obtained from the bundle - * context. + * Fires a notification for the given event type through the + * NotificationService. The event type is one of the static + * constants defined in the NotificationManager class. * - * @return all ProtocolProviderFactorys obtained from the bundle - * context + * @param eventType the event type for which we want to fire a notification + * @param loopCondition the method which will determine whether any sounds + * played as part of the specified notification will continue looping + * @return a reference to the fired notification to stop it. */ - public static Map - getProtocolProviderFactories() + private static NotificationData fireNotification( + String eventType, + Callable loopCondition) { - ServiceReference[] serRefs = null; - try - { - // get all registered provider factories - serRefs + return fireNotification(eventType, null, null, null, loopCondition); + } + + /** + * Fires a notification through the NotificationService with a + * specific event type, a specific message title and a specific message. + *

+ * Note: The uses of the method at the time of this writing do not + * take measures to stop looping sounds if the respective notifications use + * them i.e. there is implicit agreement that the notifications fired + * through the method do not loop sounds. Consequently, the method passes + * arguments to NotificationService so that sounds are played once + * only. + *

+ * + * @param eventType the event type of the notification to be fired + * @param messageTitle the title of the message to be displayed by the + * notification to be fired if such a display is supported + * @param message the message to be displayed by the notification to be + * fired if such a display is supported + */ + private static void fireNotification( + String eventType, + String messageTitle, + String message) + { + NotificationService notificationService + = NotificationWiringActivator.getNotificationService(); + + if (notificationService != null) + { + notificationService.fireNotification( + eventType, + messageTitle, + message, + null); + } + } + + /** + * Fires a message notification for the given event type through the + * NotificationService. + * + * @param eventType the event type for which we fire a notification + * @param messageTitle the title of the message + * @param message the content of the message + * @param cmdargs the value to be provided to + * {@link CommandNotificationHandler#execute(CommandNotificationAction, + * Map)} as the cmdargs argument + * @param loopCondition the method which will determine whether any sounds + * played as part of the specified notification will continue looping + * @return a reference to the fired notification to stop it. + */ + private static NotificationData fireNotification( + String eventType, + String messageTitle, + String message, + Map cmdargs, + Callable loopCondition) + { + NotificationService notificationService + = NotificationWiringActivator.getNotificationService(); + + if (notificationService == null) + return null; + else + { + Map extras = new HashMap(); + + if (cmdargs != null) + { + extras.put( + NotificationData + .COMMAND_NOTIFICATION_HANDLER_CMDARGS_EXTRA, + cmdargs); + } + if (loopCondition != null) + { + extras.put( + NotificationData + .SOUND_NOTIFICATION_HANDLER_LOOP_CONDITION_EXTRA, + loopCondition); + } + return + notificationService.fireNotification( + eventType, + messageTitle, + message, + null, + extras); + } + } + + /** + * Loads an image from a given image identifier. + * + * @param imageID The identifier of the image. + * @return The image for the given identifier. + */ + public static BufferedImage getImage(ImageID imageID) + { + /* + * If we were mapping ImageID to null, we would be using the method + * Map.containsKey. However, that does not seem to be the case. + */ + BufferedImage image = images.get(imageID); + + if (image == null) + { + URL path + = NotificationWiringActivator.getResources().getImageURL( + imageID.getId()); + + if (path != null) + { + try + { + image = ImageIO.read(path); + images.put(imageID, image); + } + catch (Exception ex) + { + logger.error("Failed to load image: " + path, ex); + } + } + } + + return image; + } + + /** + * Returns all ProtocolProviderFactorys obtained from the bundle + * context. + * + * @return all ProtocolProviderFactorys obtained from the bundle + * context + */ + public static Map + getProtocolProviderFactories() + { + ServiceReference[] serRefs = null; + try + { + // get all registered provider factories + serRefs = NotificationWiringActivator.bundleContext.getServiceReferences( ProtocolProviderFactory.class.getName(), null); @@ -364,111 +462,256 @@ private void registerDefaultNotifications() } /** - * Adds all listeners related to the given protocol provider. + * Determines whether a specific ChatRoom is private i.e. + * represents a one-to-one conversation which is not a channel. Since the + * interface {@link ChatRoom} does not expose the private property, an + * heuristic is used as a workaround: (1) a system ChatRoom is + * obviously not private and (2) a ChatRoom is private if it + * has only one ChatRoomMember who is not the local user. * - * @param protocolProvider the ProtocolProviderService + * @param chatRoom + * the ChatRoom to be determined as private or not + * @return true if the specified ChatRoom is private; + * otherwise, false */ - private void handleProviderAdded(ProtocolProviderService protocolProvider) + private static boolean isPrivate(ChatRoom chatRoom) { - if(!protocolProvider.getAccountID().isEnabled()) - return; + if (!chatRoom.isSystem() + && chatRoom.isJoined() + && (chatRoom.getMembersCount() == 1)) + { + String nickname = chatRoom.getUserNickname(); - Map supportedOperationSets - = protocolProvider.getSupportedOperationSets(); + if (nickname != null) + { + for (ChatRoomMember member : chatRoom.getMembers()) + if (nickname.equals(member.getName())) + return false; + return true; + } + } + return false; + } - // Obtain the basic instant messaging operation set. - String imOpSetClassName = OperationSetBasicInstantMessaging - .class.getName(); + /** + * Stops all sounds for the given event type. + * + * @param data the event type for which we should stop sounds. One of + * the static event types defined in this class. + */ + public static void stopSound(NotificationData data) + { + NotificationService notificationService + = NotificationWiringActivator.getNotificationService(); - if (supportedOperationSets.containsKey(imOpSetClassName)) - { - OperationSetBasicInstantMessaging im - = (OperationSetBasicInstantMessaging) - supportedOperationSets.get(imOpSetClassName); + if(notificationService != null) + notificationService.stopNotification(data); + } - //Add to all instant messaging operation sets the Message - //listener which handles all received messages. - im.addMessageListener(this); - } + /** + * Stores notification references to stop them if a notification has expired + * (e.g. to stop the dialing sound). + */ + private final Map callNotifications + = new WeakHashMap(); - // Obtain the typing notifications operation set. - String tnOpSetClassName = OperationSetTypingNotifications - .class.getName(); + /** + * The pseudo timer which is used to delay multiple typing notifications + * before receiving the message. + */ + private final Map proactiveTimer + = new HashMap(); - if (supportedOperationSets.containsKey(tnOpSetClassName)) + /** + * Implements CallListener.callEnded. Stops sounds that are playing at + * the moment if there're any. + * @param event the CallEvent + */ + public void callEnded(CallEvent event) + { + try { - OperationSetTypingNotifications tn - = (OperationSetTypingNotifications) - supportedOperationSets.get(tnOpSetClassName); + // Stop all telephony related sounds. +// stopAllTelephonySounds(); + stopSound(callNotifications.get(event.getSourceCall())); - //Add to all typing notification operation sets the Message - //listener implemented in the ContactListPanel, which handles - //all received messages. - tn.addTypingNotificationsListener(this); + // Play the hangup sound. + fireNotification(HANG_UP); } - - // Obtain file transfer operation set. - OperationSetFileTransfer fileTransferOpSet - = protocolProvider.getOperationSet(OperationSetFileTransfer.class); - - if (fileTransferOpSet != null) + catch(Throwable t) { - fileTransferOpSet.addFileTransferListener(this); + logger.error("Error notifying for call ended", t); } + } - OperationSetMultiUserChat multiChatOpSet - = protocolProvider.getOperationSet(OperationSetMultiUserChat.class); + /** + * Implements the CallChangeListener.callPeerAdded method. + * @param evt the CallPeerEvent that notifies us for the change + */ + public void callPeerAdded(CallPeerEvent evt) + { + CallPeer peer = evt.getSourceCallPeer(); - if (multiChatOpSet != null) - { - multiChatOpSet.addPresenceListener(this); - } + if(peer == null) + return; - OperationSetAdHocMultiUserChat multiAdHocChatOpSet - = protocolProvider.getOperationSet(OperationSetAdHocMultiUserChat.class); + peer.addCallPeerListener(this); + peer.addCallPeerSecurityListener(this); + peer.addCallPeerConferenceListener(this); + } - if (multiAdHocChatOpSet != null) - { - multiAdHocChatOpSet.addPresenceListener(this); - } + /** + * Implements the CallChangeListener.callPeerRemoved method. + * @param evt the CallPeerEvent that has been triggered + */ + public void callPeerRemoved(CallPeerEvent evt) + { + CallPeer peer = evt.getSourceCallPeer(); - OperationSetBasicTelephony basicTelephonyOpSet - = protocolProvider.getOperationSet(OperationSetBasicTelephony.class); + if(peer == null) + return; - if (basicTelephonyOpSet != null) - { - basicTelephonyOpSet.addCallListener(this); - } + peer.removeCallPeerListener(this); + peer.removeCallPeerSecurityListener(this); + peer.addCallPeerConferenceListener(this); } /** - * Removes all listeners related to the given protocol provider. + * {@inheritDoc} * - * @param protocolProvider the ProtocolProviderService + * Not used. */ - private void handleProviderRemoved(ProtocolProviderService protocolProvider) - { - Map supportedOperationSets - = protocolProvider.getSupportedOperationSets(); + public void callStateChanged(CallChangeEvent ev) {} - // Obtain the basic instant messaging operation set. - String imOpSetClassName = OperationSetBasicInstantMessaging - .class.getName(); + /** + * {@inheritDoc} + * + * Not used. + */ + public void conferenceFocusChanged(CallPeerConferenceEvent ev) {} - if (supportedOperationSets.containsKey(imOpSetClassName)) + /** + * Indicates that the given conference member has been added to the given + * peer. + * + * @param conferenceEvent the event + */ + public void conferenceMemberAdded(CallPeerConferenceEvent conferenceEvent) + { + try { - OperationSetBasicInstantMessaging im - = (OperationSetBasicInstantMessaging) - supportedOperationSets.get(imOpSetClassName); + CallPeer peer + = conferenceEvent + .getConferenceMember() + .getConferenceFocusCallPeer(); - //Add to all instant messaging operation sets the Message - //listener which handles all received messages. - im.removeMessageListener(this); - } + if(peer.getConferenceMemberCount() > 0) + { + CallPeerSecurityStatusEvent securityEvent + = peer.getCurrentSecuritySettings(); - // Obtain the typing notifications operation set. - String tnOpSetClassName = OperationSetTypingNotifications - .class.getName(); + if (securityEvent instanceof CallPeerSecurityOnEvent) + fireNotification(CALL_SECURITY_ON); + } + } + catch(Throwable t) + { + if (t instanceof ThreadDeath) + throw (ThreadDeath) t; + else + logger.error("Error notifying for secured call member", t); + } + } + + /** + * {@inheritDoc} + * + * Not used. + */ + public void conferenceMemberRemoved(CallPeerConferenceEvent ev) {} + + /** + * {@inheritDoc} + * + * Not used. + */ + public void fileTransferCreated(FileTransferCreatedEvent ev) {} + + /** + * {@inheritDoc} + * + * Not used. + */ + public void fileTransferRequestCanceled(FileTransferRequestEvent ev) {} + + /** + * When a request has been received we show a notification. + * + * @param event FileTransferRequestEvent + * @see FileTransferListener#fileTransferRequestReceived(FileTransferRequestEvent) + */ + public void fileTransferRequestReceived(FileTransferRequestEvent event) + { + try + { + IncomingFileTransferRequest request = event.getRequest(); + Contact sourceContact = request.getSender(); + + //Fire notification + String title = NotificationWiringActivator.getResources().getI18NString( + "service.gui.FILE_RECEIVING_FROM", + new String[]{sourceContact.getDisplayName()}); + + fireChatNotification( + sourceContact, + INCOMING_FILE, + title, + request.getFileName()); + } + catch(Throwable t) + { + logger.error("Error notifying for file transfer req received", t); + } + } + + /** + * {@inheritDoc} + * + * Not used. + */ + public void fileTransferRequestRejected(FileTransferRequestEvent ev) {} + + /** + * Adds all listeners related to the given protocol provider. + * + * @param protocolProvider the ProtocolProviderService + */ + private void handleProviderAdded(ProtocolProviderService protocolProvider) + { + if(!protocolProvider.getAccountID().isEnabled()) + return; + + Map supportedOperationSets + = protocolProvider.getSupportedOperationSets(); + + // Obtain the basic instant messaging operation set. + String imOpSetClassName = OperationSetBasicInstantMessaging + .class.getName(); + + if (supportedOperationSets.containsKey(imOpSetClassName)) + { + OperationSetBasicInstantMessaging im + = (OperationSetBasicInstantMessaging) + supportedOperationSets.get(imOpSetClassName); + + //Add to all instant messaging operation sets the Message + //listener which handles all received messages. + im.addMessageListener(this); + } + + // Obtain the typing notifications operation set. + String tnOpSetClassName = OperationSetTypingNotifications + .class.getName(); if (supportedOperationSets.containsKey(tnOpSetClassName)) { @@ -479,7 +722,7 @@ private void handleProviderRemoved(ProtocolProviderService protocolProvider) //Add to all typing notification operation sets the Message //listener implemented in the ContactListPanel, which handles //all received messages. - tn.removeTypingNotificationsListener(this); + tn.addTypingNotificationsListener(this); } // Obtain file transfer operation set. @@ -488,7 +731,7 @@ private void handleProviderRemoved(ProtocolProviderService protocolProvider) if (fileTransferOpSet != null) { - fileTransferOpSet.removeFileTransferListener(this); + fileTransferOpSet.addFileTransferListener(this); } OperationSetMultiUserChat multiChatOpSet @@ -496,7 +739,7 @@ private void handleProviderRemoved(ProtocolProviderService protocolProvider) if (multiChatOpSet != null) { - multiChatOpSet.removePresenceListener(this); + multiChatOpSet.addPresenceListener(this); } OperationSetAdHocMultiUserChat multiAdHocChatOpSet @@ -504,7 +747,7 @@ private void handleProviderRemoved(ProtocolProviderService protocolProvider) if (multiAdHocChatOpSet != null) { - multiAdHocChatOpSet.removePresenceListener(this); + multiAdHocChatOpSet.addPresenceListener(this); } OperationSetBasicTelephony basicTelephonyOpSet @@ -512,272 +755,207 @@ private void handleProviderRemoved(ProtocolProviderService protocolProvider) if (basicTelephonyOpSet != null) { - basicTelephonyOpSet.removeCallListener(this); + basicTelephonyOpSet.addCallListener(this); } } /** - * Implements the ServiceListener method. Verifies whether the - * passed event concerns a ProtocolProviderService and adds the - * corresponding listeners. + * Removes all listeners related to the given protocol provider. * - * @param event The ServiceEvent object. + * @param protocolProvider the ProtocolProviderService */ - public void serviceChanged(ServiceEvent event) + private void handleProviderRemoved(ProtocolProviderService protocolProvider) { - ServiceReference serviceRef = event.getServiceReference(); + Map supportedOperationSets + = protocolProvider.getSupportedOperationSets(); - // if the event is caused by a bundle being stopped, we don't want to - // know - if (serviceRef.getBundle().getState() == Bundle.STOPPING) + // Obtain the basic instant messaging operation set. + String imOpSetClassName = OperationSetBasicInstantMessaging + .class.getName(); + + if (supportedOperationSets.containsKey(imOpSetClassName)) { - return; + OperationSetBasicInstantMessaging im + = (OperationSetBasicInstantMessaging) + supportedOperationSets.get(imOpSetClassName); + + //Add to all instant messaging operation sets the Message + //listener which handles all received messages. + im.removeMessageListener(this); + } + + // Obtain the typing notifications operation set. + String tnOpSetClassName = OperationSetTypingNotifications + .class.getName(); + + if (supportedOperationSets.containsKey(tnOpSetClassName)) + { + OperationSetTypingNotifications tn + = (OperationSetTypingNotifications) + supportedOperationSets.get(tnOpSetClassName); + + //Add to all typing notification operation sets the Message + //listener implemented in the ContactListPanel, which handles + //all received messages. + tn.removeTypingNotificationsListener(this); } - Object service = - NotificationWiringActivator.bundleContext.getService(serviceRef); + // Obtain file transfer operation set. + OperationSetFileTransfer fileTransferOpSet + = protocolProvider.getOperationSet(OperationSetFileTransfer.class); - // we don't care if the source service is not a protocol provider - if (!(service instanceof ProtocolProviderService)) + if (fileTransferOpSet != null) { - return; + fileTransferOpSet.removeFileTransferListener(this); } - switch (event.getType()) + OperationSetMultiUserChat multiChatOpSet + = protocolProvider.getOperationSet(OperationSetMultiUserChat.class); + + if (multiChatOpSet != null) { - case ServiceEvent.REGISTERED: - this.handleProviderAdded((ProtocolProviderService) service); - break; - case ServiceEvent.UNREGISTERING: - this.handleProviderRemoved((ProtocolProviderService) service); - break; + multiChatOpSet.removePresenceListener(this); + } + + OperationSetAdHocMultiUserChat multiAdHocChatOpSet + = protocolProvider.getOperationSet(OperationSetAdHocMultiUserChat.class); + + if (multiAdHocChatOpSet != null) + { + multiAdHocChatOpSet.removePresenceListener(this); + } + + OperationSetBasicTelephony basicTelephonyOpSet + = protocolProvider.getOperationSet(OperationSetBasicTelephony.class); + + if (basicTelephonyOpSet != null) + { + basicTelephonyOpSet.removeCallListener(this); } } /** - * Fires a message notification for the given event type through the - * NotificationService. + * Implements CallListener.incomingCallReceived. When a call is received + * plays the ring phone sound to the user and gathers caller information + * that may be used by a user-specified command (incomingCall event + * trigger). * - * @param eventType the event type for which we fire a notification - * @param messageTitle the title of the message - * @param message the content of the message - * @return A reference to the fired notification to stop it. + * @param ev the CallEvent */ - public static NotificationData fireNotification(String eventType, - String messageTitle, - String message) + public void incomingCallReceived(CallEvent ev) { - NotificationService notificationService - = NotificationWiringActivator.getNotificationService(); + try + { + final Call call = ev.getSourceCall(); + CallPeer peer = call.getCallPeers().next(); + Map peerInfo = new HashMap(); + String peerName = peer.getDisplayName(); + + peerInfo.put("caller.uri", peer.getURI()); + peerInfo.put("caller.address", peer.getAddress()); + peerInfo.put("caller.name", peerName); + peerInfo.put("caller.id", peer.getPeerID()); + + NotificationData notification + = fireNotification( + INCOMING_CALL, + "", + NotificationWiringActivator.getResources() + .getI18NString( + "service.gui.INCOMING_CALL", + new String[] { peerName }), + peerInfo, + new Callable() + { + public Boolean call() + { + /* + * INCOMING_CALL should be played for a Call + * only while there is a CallPeer in the + * INCOMING_CALL state. + */ + Iterator peerIter + = call.getCallPeers(); + boolean loop = false; + + while (peerIter.hasNext()) + { + CallPeer peer = peerIter.next(); + + if (CallPeerState.INCOMING_CALL.equals( + peer.getState())) + { + loop = true; + break; + } + } + return loop; + } + }); + + if (notification != null) + callNotifications.put(call, notification); - if(notificationService == null) - return null; + call.addCallChangeListener(this); - return notificationService.fireNotification( eventType, - messageTitle, - message, - null, - null); + peer.addCallPeerListener(this); + peer.addCallPeerSecurityListener(this); + peer.addCallPeerConferenceListener(this); + } + catch(Throwable t) + { + if (t instanceof ThreadDeath) + throw (ThreadDeath) t; + else + { + logger.error( + "An error occurred while trying to notify" + + " about an incoming call", + t); + } + } } /** - * Fires a message notification for the given event type through the - * NotificationService. - * - * @param eventType the event type for which we fire a notification - * @param messageTitle the title of the message - * @param message the content of the message - * @param extra additional event data for external processing - * @return A reference to the fired notification to stop it. + * Initialize, register default notifications and start listening for + * new protocols or removed one and find any that are already registered. */ - public static NotificationData fireNotification(String eventType, - String messageTitle, - String message, - Map extra) + void init() { - NotificationService notificationService - = NotificationWiringActivator.getNotificationService(); + registerDefaultNotifications(); - if(notificationService == null) - return null; + // listens for new protocols + NotificationWiringActivator.bundleContext.addServiceListener(this); + + // enumerate currently registered protocols + for(ProtocolProviderService pp : getProtocolProviders()) + { + handleProviderAdded(pp); + } - return notificationService.fireNotification(eventType, - messageTitle, - message, - extra, - null, - null); + NotificationWiringActivator.getMediaService().addRecorderListener(this); } /** - * Fires a chat message notification for the given event type through the - * NotificationService. + * Checks if the contained call is a conference call. * - * @param chatContact the chat contact to which the chat message corresponds; - * the chat contact could be a Contact or a ChatRoom. - * @param eventType the event type for which we fire a notification - * @param messageTitle the title of the message - * @param message the content of the message + * @param call the call to check + * @return true if the contained Call is a conference + * call, otherwise - returns false. */ - public static void fireChatNotification(Object chatContact, - String eventType, - String messageTitle, - String message) + public boolean isConference(Call call) { - NotificationService notificationService - = NotificationWiringActivator.getNotificationService(); + // If we're the focus of the conference. + if (call.isConferenceFocus()) + return true; - if(notificationService == null) - return; + // If one of our peers is a conference focus, we're in a + // conference call. + Iterator callPeers = call.getCallPeers(); - NotificationAction popupActionHandler = null; - UIService uiService = NotificationWiringActivator.getUIService(); - - Chat chatPanel = null; - byte[] contactIcon = null; - if (chatContact instanceof Contact) - { - Contact contact = (Contact) chatContact; - - if(uiService != null) - chatPanel = uiService.getChat(contact); - - contactIcon = contact.getImage(); - if(contactIcon == null) - { - contactIcon = - ImageUtils.toByteArray(getImage(DEFAULT_USER_PHOTO)); - } - } - else if (chatContact instanceof ChatRoom) - { - ChatRoom chatRoom = (ChatRoom) chatContact; - - // For system rooms we don't want to send notification events. - if (chatRoom.isSystem()) - return; - - if(uiService != null) - chatPanel = uiService.getChat(chatRoom); - } - - if (chatPanel != null) - { - if (eventType.equals(INCOMING_MESSAGE) - && chatPanel.isChatFocused()) - { - popupActionHandler = notificationService - .getEventNotificationAction(eventType, - NotificationAction.ACTION_POPUP_MESSAGE); - - popupActionHandler.setEnabled(false); - } - } - - notificationService.fireNotification( eventType, - messageTitle, - message, - null, - contactIcon, - chatContact); - - if(popupActionHandler != null) - popupActionHandler.setEnabled(true); - } - - /** - * Fires a notification for the given event type through the - * NotificationService. The event type is one of the static - * constants defined in this class. - * - * @param eventType the event type for which we want to fire a notification - * @return A reference to the fired notification to stop it. - */ - public static NotificationData fireNotification(String eventType) - { - NotificationService notificationService - = NotificationWiringActivator.getNotificationService(); - - if(notificationService == null) - return null; - - return notificationService.fireNotification(eventType); - } - - /** - * Stops all sounds for the given event type. - * - * @param data the event type for which we should stop sounds. One of - * the static event types defined in this class. - */ - public static void stopSound(NotificationData data) - { - NotificationService notificationService - = NotificationWiringActivator.getNotificationService(); - - if(notificationService == null) - return; - - notificationService.stopNotification(data); - } - - /** - * Loads an image from a given image identifier. - * - * @param imageID The identifier of the image. - * @return The image for the given identifier. - */ - public static BufferedImage getImage(ImageID imageID) - { - BufferedImage image = null; - - if (loadedImages.containsKey(imageID)) - { - image = loadedImages.get(imageID); - } - else - { - URL path = NotificationWiringActivator.getResources() - .getImageURL(imageID.getId()); - - if (path != null) - { - try - { - image = ImageIO.read(path); - - loadedImages.put(imageID, image); - } - catch (Exception ex) - { - logger.error("Failed to load image: " + path, ex); - } - } - } - - return image; - } - - /** - * Checks if the contained call is a conference call. - * - * @param call the call to check - * @return true if the contained Call is a conference - * call, otherwise - returns false. - */ - public boolean isConference(Call call) - { - // If we're the focus of the conference. - if (call.isConferenceFocus()) - return true; - - // If one of our peers is a conference focus, we're in a - // conference call. - Iterator callPeers = call.getCallPeers(); - - while (callPeers.hasNext()) - { - CallPeer callPeer = callPeers.next(); + while (callPeers.hasNext()) + { + CallPeer callPeer = callPeers.next(); if (callPeer.isConferenceFocus()) return true; @@ -793,268 +971,238 @@ public boolean isConference(Call call) } /** - * Determines whether a specific ChatRoom is private i.e. - * represents a one-to-one conversation which is not a channel. Since the - * interface {@link ChatRoom} does not expose the private property, an - * heuristic is used as a workaround: (1) a system ChatRoom is - * obviously not private and (2) a ChatRoom is private if it - * has only one ChatRoomMember who is not the local user. + * Implements the + * LocalUserAdHocChatRoomPresenceListener.localUserPresenceChanged + * method * - * @param chatRoom - * the ChatRoom to be determined as private or not - * @return true if the specified ChatRoom is private; - * otherwise, false + * @param evt the LocalUserAdHocChatRoomPresenceChangeEvent that + * notified us of a presence change */ - private static boolean isPrivate(ChatRoom chatRoom) + public void localUserAdHocPresenceChanged( + LocalUserAdHocChatRoomPresenceChangeEvent evt) { - if (!chatRoom.isSystem() - && chatRoom.isJoined() - && (chatRoom.getMembersCount() == 1)) - { - String nickname = chatRoom.getUserNickname(); + String eventType = evt.getEventType(); - if (nickname != null) - { - for (ChatRoomMember member : chatRoom.getMembers()) - if (nickname.equals(member.getName())) - return false; - return true; - } + if (LocalUserAdHocChatRoomPresenceChangeEvent + .LOCAL_USER_JOINED.equals(eventType)) + { + evt.getAdHocChatRoom().addMessageListener(this); + } + else if (LocalUserAdHocChatRoomPresenceChangeEvent + .LOCAL_USER_LEFT.equals(eventType) + || LocalUserAdHocChatRoomPresenceChangeEvent + .LOCAL_USER_DROPPED.equals(eventType)) + { + evt.getAdHocChatRoom().removeMessageListener(this); } - return false; } /** - * Fired on new messages. - * @param evt the MessageReceivedEvent containing - * details on the received message + * Implements the + * LocalUserChatRoomPresenceListener.localUserPresenceChanged + * method. + * @param evt the LocalUserChatRoomPresenceChangeEvent that + * notified us */ - public void messageReceived(MessageReceivedEvent evt) + public void localUserPresenceChanged( + LocalUserChatRoomPresenceChangeEvent evt) { - try - { - // Fire notification - String title = NotificationWiringActivator.getResources().getI18NString( - "service.gui.MSG_RECEIVED", - new String[]{evt.getSourceContact().getDisplayName()}); + ChatRoom sourceChatRoom = evt.getChatRoom(); + String eventType = evt.getEventType(); - fireChatNotification( - evt.getSourceContact(), - INCOMING_MESSAGE, - title, - evt.getSourceMessage().getContent()); + if (LocalUserChatRoomPresenceChangeEvent + .LOCAL_USER_JOINED.equals(eventType)) + { + sourceChatRoom.addMessageListener(this); } - catch(Throwable t) + else if (LocalUserChatRoomPresenceChangeEvent + .LOCAL_USER_LEFT.equals(eventType) + || LocalUserChatRoomPresenceChangeEvent + .LOCAL_USER_KICKED.equals(eventType) + || LocalUserChatRoomPresenceChangeEvent + .LOCAL_USER_DROPPED.equals(eventType)) { - logger.error("Error notifying for message received", t); + sourceChatRoom.removeMessageListener(this); } } /** - * Fired when message is delivered. - * @param evt the MessageDeliveredEvent containing - * details on the delivered message + * {@inheritDoc} + * + * Not used. */ - public void messageDelivered(MessageDeliveredEvent evt) - {} + public void messageDelivered(AdHocChatRoomMessageDeliveredEvent ev) {} /** - * Fired when message deliver fail. - * @param evt the MessageDeliveryFailedEvent containing - * details on the failed message + * {@inheritDoc} + * + * Not used. */ - public void messageDeliveryFailed(MessageDeliveryFailedEvent evt) - {} + public void messageDelivered(ChatRoomMessageDeliveredEvent ev) {} /** - * When a request has been received we show a notification. + * {@inheritDoc} * - * @param event FileTransferRequestEvent - * @see FileTransferListener#fileTransferRequestReceived(FileTransferRequestEvent) + * Not used */ - public void fileTransferRequestReceived(FileTransferRequestEvent event) - { - try - { - IncomingFileTransferRequest request = event.getRequest(); - Contact sourceContact = request.getSender(); - - //Fire notification - String title = NotificationWiringActivator.getResources().getI18NString( - "service.gui.FILE_RECEIVING_FROM", - new String[]{sourceContact.getDisplayName()}); - - fireChatNotification( - sourceContact, - INCOMING_FILE, - title, - request.getFileName()); - } - catch(Throwable t) - { - logger.error("Error notifying for file transfer req received", t); - } - } + public void messageDelivered(MessageDeliveredEvent ev) {} /** - * Nothing to do here, because we already know when a file transfer is - * created. - * @param event the FileTransferCreatedEvent that notified us + * {@inheritDoc} + * + * Not used. */ - public void fileTransferCreated(FileTransferCreatedEvent event) - {} + public void messageDeliveryFailed( + AdHocChatRoomMessageDeliveryFailedEvent ev) {} /** - * Called when a new IncomingFileTransferRequest has been rejected. - * Nothing to do here, because we are the one who rejects the request. + * {@inheritDoc} * - * @param event the FileTransferRequestEvent containing the - * received request which was rejected. + * Not used. */ - public void fileTransferRequestRejected(FileTransferRequestEvent event) - {} + public void messageDeliveryFailed(ChatRoomMessageDeliveryFailedEvent ev) {} /** - * Called when an IncomingFileTransferRequest has been canceled - * from the contact who sent it. + * {@inheritDoc} * - * @param event the FileTransferRequestEvent containing the - * request which was canceled. + * Not used. */ - public void fileTransferRequestCanceled(FileTransferRequestEvent event) - {} + public void messageDeliveryFailed(MessageDeliveryFailedEvent ev) {} /** - * Informs the user what is the typing state of his chat contacts. - * - * @param event the event containing details on the typing notification + * Implements the AdHocChatRoomMessageListener.messageReceived + * method. + *
+ * @param evt the AdHocChatRoomMessageReceivedEvent that notified + * us */ - public void typingNotificationReceived(TypingNotificationEvent event) + public void messageReceived(AdHocChatRoomMessageReceivedEvent evt) { try { - Contact contact = event.getSourceContact(); + AdHocChatRoom sourceChatRoom = evt.getSourceChatRoom(); + Contact sourceParticipant = evt.getSourceChatRoomParticipant(); - // we don't care for proactive notifications, different than typing - // sometimes after closing chat we can see someone is typing us - // its just server sanding that the chat is inactive (STATE_STOPPED) - if(event.getTypingState() - != OperationSetTypingNotifications.STATE_TYPING) - return; + // Fire notification + boolean fireChatNotification; - // check whether the current chat window shows the - // chat we received a typing info for and in such case don't show - // notifications - UIService uiService = NotificationWiringActivator.getUIService(); + String nickname = sourceChatRoom.getName(); + String messageContent = evt.getMessage().getContent(); - if(uiService != null) + fireChatNotification = + (nickname == null) + || messageContent.toLowerCase().contains( + nickname.toLowerCase()); + + if (fireChatNotification) { - Chat chat = uiService.getCurrentChat(); - if(chat != null) - { - MetaContact metaContact = uiService.getChatContact(chat); + String title + = NotificationWiringActivator.getResources().getI18NString( + "service.gui.MSG_RECEIVED", + new String[] { sourceParticipant.getDisplayName() }); - if(metaContact != null && metaContact.containsContact(contact) - && chat.isChatFocused()) - { - return; - } - } + fireChatNotification( + sourceChatRoom, + INCOMING_MESSAGE, + title, + messageContent); } + } + catch(Throwable t) + { + logger.error("Error notifying for adhoc message received", t); + } + } - long currentTime = System.currentTimeMillis(); + /** + * Implements the ChatRoomMessageListener.messageReceived method. + *
+ * Obtains the corresponding ChatPanel and process the message + * there. + * @param evt the ChatRoomMessageReceivedEvent that notified us + * that a message has been received + */ + public void messageReceived(ChatRoomMessageReceivedEvent evt) + { + try + { + ChatRoom sourceChatRoom = evt.getSourceChatRoom(); + ChatRoomMember sourceMember = evt.getSourceChatRoomMember(); + + // Fire notification + boolean fireChatNotification; + + String messageContent = evt.getMessage().getContent(); - if (this.proactiveTimer.size() > 0) + /* + * It is uncommon for IRC clients to display popup notifications for + * messages which are sent to public channels and which do not mention + * the nickname of the local user. + */ + if (sourceChatRoom.isSystem() + || isPrivate(sourceChatRoom) + || (messageContent == null)) + fireChatNotification = true; + else { - // first remove contacts that have been here longer than the - // timeout to avoid memory leaks - Iterator> entries - = this.proactiveTimer.entrySet().iterator(); - while (entries.hasNext()) - { - Map.Entry entry = entries.next(); - Long lastNotificationDate = entry.getValue(); - if (lastNotificationDate.longValue() + 30000 < currentTime) - { - // The entry is outdated - entries.remove(); - } - } + String nickname = sourceChatRoom.getUserNickname(); - // Now, check if the contact is still in the map - if (this.proactiveTimer.containsKey(contact)) - { - // We already notified the others about this - return; - } + int atIx = -1; + + if(nickname != null) + atIx = nickname.indexOf("@"); + + fireChatNotification = + (nickname == null) + || messageContent.toLowerCase().contains( + nickname.toLowerCase()) + || ((atIx == -1)? false : messageContent.toLowerCase() + .contains(nickname.substring(0, atIx).toLowerCase())); } - this.proactiveTimer.put(contact, currentTime); + if (fireChatNotification) + { + String title + = NotificationWiringActivator.getResources().getI18NString( + "service.gui.MSG_RECEIVED", + new String[] { sourceMember.getName() }); - fireChatNotification( - contact, - PROACTIVE_NOTIFICATION, - contact.getDisplayName(), - NotificationWiringActivator.getResources() - .getI18NString("service.gui.PROACTIVE_NOTIFICATION")); + fireChatNotification( + sourceChatRoom, + INCOMING_MESSAGE, + title, + messageContent); + } } catch(Throwable t) { - logger.error("Error notifying for typing evt received", t); + logger.error("Error notifying for chat room message received", t); } } /** - * Called to indicate that sending typing notification has failed. - * - * @param event a TypingNotificationEvent containing the sender - * of the notification and its type. - */ - public void typingNotificationDeliveryFailed(TypingNotificationEvent event) - {} - - /** - * Implements CallListener.incomingCallReceived. When a call is received - * plays the ring phone sound to the user and gathers caller information - * that may be used by a user-specified command (incomingCall event trigger). - * @param event the CallEvent + * Fired on new messages. + * @param evt the MessageReceivedEvent containing + * details on the received message */ - public void incomingCallReceived(CallEvent event) + public void messageReceived(MessageReceivedEvent evt) { try { - Call call = event.getSourceCall(); - CallPeer firstPeer = call.getCallPeers().next(); - String peerName = firstPeer.getDisplayName(); - - Map peerInfo = new HashMap(); - peerInfo.put("caller.uri", firstPeer.getURI()); - peerInfo.put("caller.address", firstPeer.getAddress()); - peerInfo.put("caller.name", firstPeer.getDisplayName()); - peerInfo.put("caller.id", firstPeer.getPeerID()); - - callNotifications.put(event.getSourceCall(), - fireNotification( - INCOMING_CALL, - "", - NotificationWiringActivator.getResources() - .getI18NString("service.gui.INCOMING_CALL", - new String[]{peerName}), - peerInfo)); - - call.addCallChangeListener(this); + // Fire notification + String title = NotificationWiringActivator.getResources().getI18NString( + "service.gui.MSG_RECEIVED", + new String[]{evt.getSourceContact().getDisplayName()}); - if(call.getCallPeers().hasNext()) - { - CallPeer peer = call.getCallPeers().next(); - peer.addCallPeerListener(this); - peer.addCallPeerSecurityListener(this); - peer.addCallPeerConferenceListener(this); - } + fireChatNotification( + evt.getSourceContact(), + INCOMING_MESSAGE, + title, + evt.getSourceMessage().getContent()); } catch(Throwable t) { - logger.error("Error notifying for incoming call received", t); + logger.error("Error notifying for message received", t); } } @@ -1077,87 +1225,64 @@ public void outgoingCallCreated(CallEvent event) } /** - * Implements CallListener.callEnded. Stops sounds that are playing at - * the moment if there're any. - * @param event the CallEvent - */ - public void callEnded(CallEvent event) - { - try - { - // Stop all telephony related sounds. -// stopAllTelephonySounds(); - stopSound(callNotifications.get(event.getSourceCall())); - - // Play the hangup sound. - fireNotification(HANG_UP); - } - catch(Throwable t) - { - logger.error("Error notifying for call ended", t); - } - } - - /** - * Implements the CallChangeListener.callPeerAdded method. - * @param evt the CallPeerEvent that notifies us for the change + * {@inheritDoc} + * + * Not used. */ - public void callPeerAdded(CallPeerEvent evt) - { - CallPeer peer = evt.getSourceCallPeer(); - - if(peer == null) - return; - - peer.addCallPeerListener(this); - peer.addCallPeerSecurityListener(this); - peer.addCallPeerConferenceListener(this); - } + public void peerAddressChanged(CallPeerChangeEvent ev) {} /** - * Implements the CallChangeListener.callPeerRemoved method. - * @param evt the CallPeerEvent that has been triggered + * {@inheritDoc} + * + * Not used. */ - public void callPeerRemoved(CallPeerEvent evt) - { - CallPeer peer = evt.getSourceCallPeer(); - - if(peer == null) - return; - - peer.removeCallPeerListener(this); - peer.removeCallPeerSecurityListener(this); - peer.addCallPeerConferenceListener(this); - } + public void peerDisplayNameChanged(CallPeerChangeEvent ev) {} /** - * Call state changed. - * @param evt the CallChangeEvent instance containing the source + * {@inheritDoc} + * + * Not used. */ - public void callStateChanged(CallChangeEvent evt) - { - } + public void peerImageChanged(CallPeerChangeEvent ev) {} /** * Fired when peer's state is changed * - * @param evt fired CallPeerEvent + * @param ev fired CallPeerEvent */ - public void peerStateChanged(CallPeerChangeEvent evt) + public void peerStateChanged(CallPeerChangeEvent ev) { try { - CallPeer sourcePeer = evt.getSourceCallPeer(); - Call call = sourcePeer.getCall(); - CallPeerState newState = (CallPeerState) evt.getNewValue(); - CallPeerState oldState = (CallPeerState) evt.getOldValue(); + final CallPeer peer = ev.getSourceCallPeer(); + Call call = peer.getCall(); + CallPeerState newState = (CallPeerState) ev.getNewValue(); + CallPeerState oldState = (CallPeerState) ev.getOldValue(); // Play the dialing audio when in connecting and initiating call state. // Stop the dialing audio when we enter any other state. - if (newState == CallPeerState.INITIATING_CALL - || newState == CallPeerState.CONNECTING) + if ((newState == CallPeerState.INITIATING_CALL) + || (newState == CallPeerState.CONNECTING)) { - callNotifications.put(call, fireNotification(DIALING)); + NotificationData notification + = fireNotification( + DIALING, + new Callable() + { + public Boolean call() + { + CallPeerState state = peer.getState(); + + return + CallPeerState.INITIATING_CALL.equals( + state) + || CallPeerState.CONNECTING.equals( + state); + } + }); + + if (notification != null) + callNotifications.put(call, notification); } else { @@ -1170,20 +1295,48 @@ public void peerStateChanged(CallPeerChangeEvent evt) //need to fire a notification here. && oldState != CallPeerState.CONNECTING_WITH_EARLY_MEDIA) { - callNotifications.put(call, fireNotification(OUTGOING_CALL)); + NotificationData notification + = fireNotification( + OUTGOING_CALL, + new Callable() + { + public Boolean call() + { + return + CallPeerState.ALERTING_REMOTE_SIDE + .equals(peer.getState()); + } + }); + + if (notification != null) + callNotifications.put(call, notification); } else if (newState == CallPeerState.BUSY) { // We start the busy sound only if we're in a simple call. if (!isConference(call)) { - callNotifications.put(call, fireNotification(BUSY_CALL)); + NotificationData notification + = fireNotification( + BUSY_CALL, + new Callable() + { + public Boolean call() + { + return + CallPeerState.BUSY.equals( + peer.getState()); + } + }); + + if (notification != null) + callNotifications.put(call, notification); } } - else if (newState == CallPeerState.DISCONNECTED - || newState == CallPeerState.FAILED) + else if ((newState == CallPeerState.DISCONNECTED) + || (newState == CallPeerState.FAILED)) { - callNotifications.put(call, fireNotification(HANG_UP)); + fireNotification(HANG_UP); } } catch(Throwable t) @@ -1193,417 +1346,413 @@ else if (newState == CallPeerState.DISCONNECTED } /** - * Fired when peer's display name is changed + * {@inheritDoc} * - * @param evt fired CallPeerEvent + * Not used. */ - public void peerDisplayNameChanged(CallPeerChangeEvent evt) - {} + public void peerTransportAddressChanged(CallPeerChangeEvent ev) {} /** - * Fired when peer's address is changed - * - * @param evt fired CallPeerEvent - */ - public void peerAddressChanged(CallPeerChangeEvent evt) - {} - - /** - * Fired when peer's transport is changed - * - * @param evt fired CallPeerEvent - */ - public void peerTransportAddressChanged(CallPeerChangeEvent evt) - {} - - /** - * Fired when peer's image is changed + * Notifies that a specific Recorder has + * stopped recording the media associated with it. * - * @param evt fired CallPeerEvent - */ - public void peerImageChanged(CallPeerChangeEvent evt) - {} - - /** - * When a securityOnEvent is received. - * @param evt the event we received + * @param recorder the Recorder which has stopped recording its + * associated media */ - public void securityOn(CallPeerSecurityOnEvent evt) + public void recorderStopped(Recorder recorder) { try { - CallPeer peer = (CallPeer) evt.getSource(); + ResourceManagementService resources + = NotificationWiringActivator.getResources(); - if((evt.getSecurityController().requiresSecureSignalingTransport() - && peer.getProtocolProvider().isSignalingTransportSecure()) - || !evt.getSecurityController().requiresSecureSignalingTransport()) - { - fireNotification(CALL_SECURITY_ON); - } + fireNotification( + CALL_SAVED, + resources.getI18NString( + "plugin.callrecordingconfig.CALL_SAVED"), + resources.getI18NString( + "plugin.callrecordingconfig.CALL_SAVED_TO", + new String[] { recorder.getFilename() })); } catch(Throwable t) { - logger.error("Error for notify for security event", t); + if (t instanceof ThreadDeath) + throw (ThreadDeath) t; + else + { + logger.error( + "An error occurred while trying to notify that" + + " the recording of a call has stopped.", + t); + } } } /** - * Indicates the new state through the security indicator components. - * @param securityOffEvent the event we received + * Register all default notifications. */ - public void securityOff(CallPeerSecurityOffEvent securityOffEvent) - {} + private void registerDefaultNotifications() + { + NotificationService notificationService + = NotificationWiringActivator.getNotificationService(); - /** - * The handler for the security event received. The security event - * represents a timeout trying to establish a secure connection. - * Most probably the other peer doesn't support it. - * - * @param securityTimeoutEvent - * the security timeout event received - */ - public void securityTimeout( - CallPeerSecurityTimeoutEvent securityTimeoutEvent) - {} + if(notificationService == null) + return; - /** - * The handler for the security event received. The security event - * for starting establish a secure connection. - * - * @param securityNegotiationStartedEvent - * the security started event received - */ - public void securityNegotiationStarted( - CallPeerSecurityNegotiationStartedEvent securityNegotiationStartedEvent) - {} + // Register incoming message notifications. + notificationService.registerDefaultNotificationForEvent( + INCOMING_MESSAGE, + NotificationAction.ACTION_POPUP_MESSAGE, + null, + null); - /** - * Processes the received security message. - * @param event the event we received - */ - public void securityMessageRecieved(CallPeerSecurityMessageEvent event) - { - try - { - int severity = event.getEventSeverity(); + notificationService.registerDefaultNotificationForEvent( + INCOMING_MESSAGE, + new SoundNotificationAction( + SoundProperties.INCOMING_MESSAGE, -1, true, false, false)); + + // Register incoming call notifications. + notificationService.registerDefaultNotificationForEvent( + INCOMING_CALL, + NotificationAction.ACTION_POPUP_MESSAGE, + null, + null); - String messageTitle = null; + SoundNotificationAction inCallSoundHandler + = new SoundNotificationAction( + SoundProperties.INCOMING_CALL, 2000, true, true, true); - switch (severity) - { - // Don't play alert sound for Info or warning. - case CallPeerSecurityMessageEvent.INFORMATION: - { - messageTitle = NotificationWiringActivator.getResources() - .getI18NString("service.gui.SECURITY_INFO"); - break; - } - case CallPeerSecurityMessageEvent.WARNING: - { - messageTitle = NotificationWiringActivator.getResources() - .getI18NString("service.gui.SECURITY_WARNING"); - break; - } - // Alert sound indicates: security cannot established - case CallPeerSecurityMessageEvent.SEVERE: - case CallPeerSecurityMessageEvent.ERROR: - { - messageTitle = NotificationWiringActivator.getResources() - .getI18NString("service.gui.SECURITY_ERROR"); - fireNotification(CALL_SECURITY_ERROR); - } - } + notificationService.registerDefaultNotificationForEvent( + INCOMING_CALL, + inCallSoundHandler); - fireNotification( + // Register outgoing call notifications. + SoundNotificationAction outCallSoundHandler + = new SoundNotificationAction( + SoundProperties.OUTGOING_CALL, 3000, false, true, false); + + notificationService.registerDefaultNotificationForEvent( + OUTGOING_CALL, + outCallSoundHandler); + + // Register busy call notifications. + notificationService.registerDefaultNotificationForEvent( + BUSY_CALL, + new SoundNotificationAction( + SoundProperties.BUSY, + 1, + false, true, false)); + + // Register dial notifications. + SoundNotificationAction dialSoundHandler + = new SoundNotificationAction( + SoundProperties.DIALING, -1, false, true, false); + + notificationService.registerDefaultNotificationForEvent( + DIALING, + dialSoundHandler); + + // Register the hangup sound notification. + notificationService.registerDefaultNotificationForEvent( + HANG_UP, + new SoundNotificationAction( + SoundProperties.HANG_UP, + -1, + false, true, false)); + + // Register proactive notifications. + notificationService.registerDefaultNotificationForEvent( + PROACTIVE_NOTIFICATION, + NotificationAction.ACTION_POPUP_MESSAGE, + null, + null); + + // Register warning message notifications. + notificationService.registerDefaultNotificationForEvent( SECURITY_MESSAGE, - messageTitle, - event.getI18nMessage()); - } - catch(Throwable t) - { - logger.error("Error notifying for security message received", t); - } - } + NotificationAction.ACTION_POPUP_MESSAGE, + null, + null); - /** - * Implements the ChatRoomMessageListener.messageReceived method. - *
- * Obtains the corresponding ChatPanel and process the message - * there. - * @param evt the ChatRoomMessageReceivedEvent that notified us - * that a message has been received - */ - public void messageReceived(ChatRoomMessageReceivedEvent evt) - { - try - { - ChatRoom sourceChatRoom = evt.getSourceChatRoom(); - ChatRoomMember sourceMember = evt.getSourceChatRoomMember(); + // Register sound notification for security state on during a call. + notificationService.registerDefaultNotificationForEvent( + CALL_SECURITY_ON, + new SoundNotificationAction( + SoundProperties.CALL_SECURITY_ON, -1, + false, true, false)); - // Fire notification - boolean fireChatNotification; + // Register sound notification for security state off during a call. + notificationService.registerDefaultNotificationForEvent( + CALL_SECURITY_ERROR, + new SoundNotificationAction( + SoundProperties.CALL_SECURITY_ERROR, -1, + false, true, false)); - String messageContent = evt.getMessage().getContent(); + // Register sound notification for incoming files. + notificationService.registerDefaultNotificationForEvent( + INCOMING_FILE, + NotificationAction.ACTION_POPUP_MESSAGE, + null, + null); - /* - * It is uncommon for IRC clients to display popup notifications for - * messages which are sent to public channels and which do not mention - * the nickname of the local user. - */ - if (sourceChatRoom.isSystem() - || isPrivate(sourceChatRoom) - || (messageContent == null)) - fireChatNotification = true; - else + notificationService.registerDefaultNotificationForEvent( + INCOMING_FILE, + new SoundNotificationAction( + SoundProperties.INCOMING_FILE, -1, + true, false, false)); + + // Register notification for saved calls. + notificationService.registerDefaultNotificationForEvent( + CALL_SAVED, + NotificationAction.ACTION_POPUP_MESSAGE, + null, + null); + } + + /** + * Processes the received security message. + * @param ev the event we received + */ + public void securityMessageRecieved(CallPeerSecurityMessageEvent ev) + { + try + { + String messageTitleKey; + + switch (ev.getEventSeverity()) { - String nickname = sourceChatRoom.getUserNickname(); + // Don't play alert sound for Info or warning. + case CallPeerSecurityMessageEvent.INFORMATION: + messageTitleKey = "service.gui.SECURITY_INFO"; + break; - int atIx = -1; + case CallPeerSecurityMessageEvent.WARNING: + messageTitleKey = "service.gui.SECURITY_WARNING"; + break; - if(nickname != null) - atIx = nickname.indexOf("@"); + // Security cannot be established! Play an alert sound. + case CallPeerSecurityMessageEvent.SEVERE: + case CallPeerSecurityMessageEvent.ERROR: + messageTitleKey = "service.gui.SECURITY_ERROR"; + fireNotification(CALL_SECURITY_ERROR); + break; - fireChatNotification = - (nickname == null) - || messageContent.toLowerCase().contains( - nickname.toLowerCase()) - || ((atIx == -1)? false : messageContent.toLowerCase() - .contains(nickname.substring(0, atIx).toLowerCase())); + default: + /* + * Whatever other severity there is or will be, we do not how to + * react to it yet. + */ + messageTitleKey = null; } - if (fireChatNotification) + if (messageTitleKey != null) { - String title - = NotificationWiringActivator.getResources().getI18NString( - "service.gui.MSG_RECEIVED", - new String[] { sourceMember.getName() }); - - fireChatNotification( - sourceChatRoom, - INCOMING_MESSAGE, - title, - messageContent); + fireNotification( + SECURITY_MESSAGE, + NotificationWiringActivator.getResources() + .getI18NString(messageTitleKey), + ev.getI18nMessage()); } } catch(Throwable t) { - logger.error("Error notifying for chat room message received", t); + if (t instanceof ThreadDeath) + throw (ThreadDeath) t; + else + { + logger.error( + "An error occurred while trying to notify" + + " about a security message", + t); + } } } /** - * Implements the ChatRoomMessageListener.messageDelivered method. - *
- * @param evt the ChatRoomMessageDeliveredEvent that notified us - * that the message was delivered to its destination + * {@inheritDoc} + * + * Not used. */ - public void messageDelivered(ChatRoomMessageDeliveredEvent evt) - {} + public void securityNegotiationStarted( + CallPeerSecurityNegotiationStartedEvent ev) {} /** - * Implements the ChatRoomMessageListener.messageDeliveryFailed - * method. - *
- * @param evt the ChatRoomMessageDeliveryFailedEvent that notified - * us of a delivery failure + * {@inheritDoc} + * + * Not used. */ - public void messageDeliveryFailed(ChatRoomMessageDeliveryFailedEvent evt) - {} + public void securityOff(CallPeerSecurityOffEvent ev) {} /** - * Implements the - * LocalUserChatRoomPresenceListener.localUserPresenceChanged - * method. - * @param evt the LocalUserChatRoomPresenceChangeEvent that - * notified us + * When a securityOnEvent is received. + * @param ev the event we received */ - public void localUserPresenceChanged( - LocalUserChatRoomPresenceChangeEvent evt) + public void securityOn(CallPeerSecurityOnEvent ev) { - ChatRoom sourceChatRoom = evt.getChatRoom(); - String eventType = evt.getEventType(); - - if (LocalUserChatRoomPresenceChangeEvent - .LOCAL_USER_JOINED.equals(eventType)) + try { - sourceChatRoom.addMessageListener(this); + SrtpControl securityController = ev.getSecurityController(); + CallPeer peer = (CallPeer) ev.getSource(); + + if(!securityController.requiresSecureSignalingTransport() + || peer.getProtocolProvider().isSignalingTransportSecure()) + { + fireNotification(CALL_SECURITY_ON); + } } - else if (LocalUserChatRoomPresenceChangeEvent - .LOCAL_USER_LEFT.equals(eventType) - || LocalUserChatRoomPresenceChangeEvent - .LOCAL_USER_KICKED.equals(eventType) - || LocalUserChatRoomPresenceChangeEvent - .LOCAL_USER_DROPPED.equals(eventType)) + catch(Throwable t) { - sourceChatRoom.removeMessageListener(this); + if (t instanceof ThreadDeath) + throw (ThreadDeath) t; + else + { + logger.error( + "An error occurred while trying to notify" + + " about a security-related event", + t); + } } } /** - * Implements the - * LocalUserAdHocChatRoomPresenceListener.localUserPresenceChanged - * method + * {@inheritDoc} * - * @param evt the LocalUserAdHocChatRoomPresenceChangeEvent that - * notified us of a presence change + * Not used. */ - public void localUserAdHocPresenceChanged( - LocalUserAdHocChatRoomPresenceChangeEvent evt) - { - String eventType = evt.getEventType(); - - if (LocalUserAdHocChatRoomPresenceChangeEvent - .LOCAL_USER_JOINED.equals(eventType)) - { - evt.getAdHocChatRoom().addMessageListener(this); - } - else if (LocalUserAdHocChatRoomPresenceChangeEvent - .LOCAL_USER_LEFT.equals(eventType) - || LocalUserAdHocChatRoomPresenceChangeEvent - .LOCAL_USER_DROPPED.equals(eventType)) - { - evt.getAdHocChatRoom().removeMessageListener(this); - } - } + public void securityTimeout(CallPeerSecurityTimeoutEvent ev) {} /** - * Implements the AdHocChatRoomMessageListener.messageReceived - * method. - *
- * @param evt the AdHocChatRoomMessageReceivedEvent that notified - * us + * Implements the ServiceListener method. Verifies whether the + * passed event concerns a ProtocolProviderService and adds the + * corresponding listeners. + * + * @param event The ServiceEvent object. */ - public void messageReceived(AdHocChatRoomMessageReceivedEvent evt) + public void serviceChanged(ServiceEvent event) { - try - { - AdHocChatRoom sourceChatRoom = evt.getSourceChatRoom(); - Contact sourceParticipant = evt.getSourceChatRoomParticipant(); - - // Fire notification - boolean fireChatNotification; + ServiceReference serviceRef = event.getServiceReference(); - String nickname = sourceChatRoom.getName(); - String messageContent = evt.getMessage().getContent(); + // if the event is caused by a bundle being stopped, we don't want to + // know + if (serviceRef.getBundle().getState() == Bundle.STOPPING) + return; - fireChatNotification = - (nickname == null) - || messageContent.toLowerCase().contains( - nickname.toLowerCase()); + Object service + = NotificationWiringActivator.bundleContext.getService(serviceRef); - if (fireChatNotification) + // we don't care if the source service is not a protocol provider + if (service instanceof ProtocolProviderService) + { + switch (event.getType()) { - String title - = NotificationWiringActivator.getResources().getI18NString( - "service.gui.MSG_RECEIVED", - new String[] { sourceParticipant.getDisplayName() }); - - fireChatNotification( - sourceChatRoom, - INCOMING_MESSAGE, - title, - messageContent); + case ServiceEvent.REGISTERED: + handleProviderAdded((ProtocolProviderService) service); + break; + case ServiceEvent.UNREGISTERING: + handleProviderRemoved((ProtocolProviderService) service); + break; } } - catch(Throwable t) - { - logger.error("Error notifying for adhoc message received", t); - } } /** - * Implements the ChatRoomMessageListener.messageDelivered method. - *
- * @param evt the ChatRoomMessageDeliveredEvent that notified us - * that the message was delivered to its destination - */ - public void messageDelivered(AdHocChatRoomMessageDeliveredEvent evt) - {} - - /** - * Implements AdHocChatRoomMessageListener.messageDeliveryFailed - * method. - *
- * In the conversation area shows an error message, explaining the problem. - * @param evt the AdHocChatRoomMessageDeliveryFailedEvent that - * notified us - */ - public void messageDeliveryFailed(AdHocChatRoomMessageDeliveryFailedEvent evt) - {} - - /** - * Call peer has changed. - * @param conferenceEvent - * a CallPeerConferenceEvent with ID - * CallPeerConferenceEvent#CONFERENCE_FOCUS_CHANGED + * {@inheritDoc} + * + * Not used. */ - public void conferenceFocusChanged(CallPeerConferenceEvent conferenceEvent) - {} + public void typingNotificationDeliveryFailed(TypingNotificationEvent ev) {} /** - * Indicates that the given conference member has been added to the given - * peer. + * Informs the user what is the typing state of his chat contacts. * - * @param conferenceEvent the event + * @param ev the event containing details on the typing notification */ - public void conferenceMemberAdded(CallPeerConferenceEvent conferenceEvent) + public void typingNotificationReceived(TypingNotificationEvent ev) { try { - CallPeer peer - = conferenceEvent - .getConferenceMember() - .getConferenceFocusCallPeer(); + Contact contact = ev.getSourceContact(); - if(peer.getConferenceMemberCount() > 0) + // we don't care for proactive notifications, different than typing + // sometimes after closing chat we can see someone is typing us + // its just server sanding that the chat is inactive (STATE_STOPPED) + if(ev.getTypingState() + != OperationSetTypingNotifications.STATE_TYPING) { - CallPeerSecurityStatusEvent securityEvent - = peer.getCurrentSecuritySettings(); + return; + } - if (securityEvent instanceof CallPeerSecurityOnEvent) - fireNotification(CALL_SECURITY_ON); + // check whether the current chat window shows the + // chat we received a typing info for and in such case don't show + // notifications + UIService uiService = NotificationWiringActivator.getUIService(); + + if(uiService != null) + { + Chat chat = uiService.getCurrentChat(); + + if(chat != null) + { + MetaContact metaContact = uiService.getChatContact(chat); + + if((metaContact != null) + && metaContact.containsContact(contact) + && chat.isChatFocused()) + { + return; + } + } } - } - catch(Throwable t) - { - if (t instanceof ThreadDeath) - throw (ThreadDeath) t; - else - logger.error("Error notifying for secured call member", t); - } - } - /** - * Indicates that the given conference member has been removed from the - * given peer. - * - * @param conferenceEvent the event - */ - public void conferenceMemberRemoved(CallPeerConferenceEvent conferenceEvent) - {} + long currentTime = System.currentTimeMillis(); - /** - * Notifies that a specific Recorder has - * stopped recording the media associated with it. - * - * @param recorder the Recorder which has stopped recording its - * associated media - */ - public void recorderStopped(Recorder recorder) - { - try - { - fireNotification( - CALL_SAVED, - NotificationWiringActivator.getResources().getI18NString( - "plugin.callrecordingconfig.CALL_SAVED"), + if (proactiveTimer.size() > 0) + { + // first remove contacts that have been here longer than the + // timeout to avoid memory leaks + Iterator> entries + = proactiveTimer.entrySet().iterator(); + + while (entries.hasNext()) + { + Map.Entry entry = entries.next(); + Long lastNotificationDate = entry.getValue(); + + if (lastNotificationDate.longValue() + 30000 < currentTime) + { + // The entry is outdated + entries.remove(); + } + } + + // Now, check if the contact is still in the map + if (proactiveTimer.containsKey(contact)) + { + // We already notified the others about this + return; + } + } + + proactiveTimer.put(contact, currentTime); + + fireChatNotification( + contact, + PROACTIVE_NOTIFICATION, + contact.getDisplayName(), NotificationWiringActivator.getResources().getI18NString( - "plugin.callrecordingconfig.CALL_SAVED_TO", - new String[] { recorder.getFilename() })); + "service.gui.PROACTIVE_NOTIFICATION")); } catch(Throwable t) { - logger.error("Error notifying for recorder stopped", t); + if (t instanceof ThreadDeath) + throw (ThreadDeath) t; + else + { + logger.error( + "An error occurred while handling" + + " a typing notification.", + t); + } } } } diff --git a/src/net/java/sip/communicator/plugin/reconnectplugin/ReconnectPluginActivator.java b/src/net/java/sip/communicator/plugin/reconnectplugin/ReconnectPluginActivator.java index b8983dd07..17958d29d 100644 --- a/src/net/java/sip/communicator/plugin/reconnectplugin/ReconnectPluginActivator.java +++ b/src/net/java/sip/communicator/plugin/reconnectplugin/ReconnectPluginActivator.java @@ -606,7 +606,6 @@ private void notify(String title, String i18nKey, String[] params) NETWORK_NOTIFICATIONS, title, getResources().getI18NString(i18nKey, params), - null, null); } diff --git a/src/net/java/sip/communicator/service/notification/CommandNotificationHandler.java b/src/net/java/sip/communicator/service/notification/CommandNotificationHandler.java index 854016614..e59e73a38 100644 --- a/src/net/java/sip/communicator/service/notification/CommandNotificationHandler.java +++ b/src/net/java/sip/communicator/service/notification/CommandNotificationHandler.java @@ -19,10 +19,12 @@ public interface CommandNotificationHandler { /** * Executes the program pointed by the descriptor. + * * @param action the action to act upon * @param cmdargs arguments that are passed to the command line specified * in the action */ - public void execute(CommandNotificationAction action, - Map cmdargs); + public void execute( + CommandNotificationAction action, + Map cmdargs); } diff --git a/src/net/java/sip/communicator/service/notification/NotificationData.java b/src/net/java/sip/communicator/service/notification/NotificationData.java index d88b6e6f5..fb88de082 100644 --- a/src/net/java/sip/communicator/service/notification/NotificationData.java +++ b/src/net/java/sip/communicator/service/notification/NotificationData.java @@ -16,34 +16,71 @@ */ public class NotificationData { + /** + * The name/key of the NotificationData extra which is provided to + * {@link CommandNotificationHandler#execute(CommandNotificationAction, + * Map)} i.e. a Map<String,String> which is known by the + * (argument) name cmdargs. + */ + public static final String COMMAND_NOTIFICATION_HANDLER_CMDARGS_EXTRA + = "CommandNotificationHandler.cmdargs"; + + /** + * The name/key of the NotificationData extra which is provided to + * {@link PopupMessageNotificationHandler#popupMessage( + * PopupMessageNotificationAction, String, String, byte[], Object)} i.e. an + * Object which is known by the (argument) name tag. + */ + public static final String POPUP_MESSAGE_HANDLER_TAG_EXTRA + = "PopupMessageNotificationHandler.tag"; + + /** + * The name/key of the NotificationData extra which is provided to + * {@link SoundNotificationHandler} i.e. a Callable<Boolean> + * which is known as the condition which determines whether looping sounds + * are to continue playing. + */ + public static final String SOUND_NOTIFICATION_HANDLER_LOOP_CONDITION_EXTRA + = "SoundNotificationHandler.loopCondition"; + private final String eventType; - private final String title; - private final String message; - private final Map extra; + + /** + * The {@link NotificationHandler}-specific extras provided to this + * instance. The keys are among the XXX_EXTRA constants defined by + * the NotificationData class. + */ + private final Map extras; + private final byte[] icon; - private final Object tag; + private final String message; + private final String title; /** * Creates a new instance of this class. * * @param eventType the type of the event that we'd like to fire a - * notification for. + * 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 extra additional data (such as caller information) + * systray or log notification.) * @param icon the icon to show in the notification if and where appropriate - * @param tag additional info to be used by the notification handler + * @param extras additional/extra {@link NotificationHandler}-specific data + * to be provided by the new instance to the various + * NotificationHandlers */ - NotificationData(String eventType, String title, String message, - Map extra, byte[] icon, Object tag) + NotificationData( + String eventType, + String title, + String message, + byte[] icon, + Map extras) { this.eventType = eventType; this.title = title; this.message = message; - this.extra = extra; this.icon = icon; - this.tag = tag; + this.extras = extras; } /** @@ -57,53 +94,61 @@ public String getEventType() } /** - * Gets the title of the given message. - * - * @return the title + * Gets the {@link NotificationHandler}-specific extras provided to this + * instance. + * + * @return the NotificationHandler-specific extras provided to this + * instance. The keys are among the XXX_EXTRA constants defined by + * the NotificationData class */ - String getTitle() + Map getExtras() { - return title; + return Collections.unmodifiableMap(extras); } /** - * Gets the message to use if and where appropriate (e.g. with systray or - * log notification). - * - * @return the message + * Gets the {@link NotificationHandler}-specific extra provided to this + * instance associated with a specific key. + * + * @param key the key whose associated NotificationHandler-specific + * extra is to be returned. Well known keys are defined by the + * NotificationData class as the XXX_EXTRA constants. + * @return the NotificationHandler-specific extra provided to this + * instance associated with the specified key */ - String getMessage() + public Object getExtra(String key) { - return message; + return (extras == null) ? null : extras.get(key); } /** - * Gets additional data (such as caller information). + * Gets the icon to show in the notification if and where appropriate. * - * @return the extra data + * @return the icon */ - public Map getExtra() + byte[] getIcon() { - return extra; + return icon; } /** - * Gets the icon to show in the notification if and where appropriate. + * Gets the message to use if and where appropriate (e.g. with systray or + * log notification). * - * @return the icon + * @return the message */ - byte[] getIcon() + String getMessage() { - return icon; + return message; } /** - * Gets additional info to be used by the notification handler. + * Gets the title of the given message. * - * @return the tag + * @return the title */ - Object getTag() + String getTitle() { - return tag; + return title; } } diff --git a/src/net/java/sip/communicator/service/notification/NotificationService.java b/src/net/java/sip/communicator/service/notification/NotificationService.java index c0b61ea49..5de9a3127 100644 --- a/src/net/java/sip/communicator/service/notification/NotificationService.java +++ b/src/net/java/sip/communicator/service/notification/NotificationService.java @@ -235,6 +235,7 @@ public void removeNotificationChangeListener( *

* This method does nothing if the given eventType is not contained * in the list of registered event types. + *

* * @param eventType the type of the event that we'd like to fire a * notification for. @@ -243,7 +244,6 @@ public void removeNotificationChangeListener( * @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 tag additional info to be used by the notification handler * @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. @@ -251,8 +251,7 @@ public void removeNotificationChangeListener( public NotificationData fireNotification( String eventType, String messageTitle, String message, - byte[] icon, - Object tag); + byte[] icon); /** * Fires all notifications registered for the specified eventType @@ -268,19 +267,21 @@ public NotificationData fireNotification( String eventType, * (e.g. with systray) * @param message the message to use if and where appropriate (e.g. with * systray or log notification.) - * @param extra the extra data to pass (especially for Command execution) * @param icon the icon to show in the notification if and where appropriate - * @param tag additional info to be used by the notification handler + * @param extras additional/extra {@link NotificationHandler}-specific data + * to be provided to the firing of the specified notification(s). The + * well-known keys are defined by the NotificationData + * XXX_EXTRA 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 messageTitle, - String message, - Map extra, - byte[] icon, - Object tag); + public NotificationData fireNotification( + String eventType, + String messageTitle, + String message, + byte[] icon, + Map extras); /** * Fires all notifications registered for the specified eventType diff --git a/src/net/java/sip/communicator/service/notification/NotificationServiceImpl.java b/src/net/java/sip/communicator/service/notification/NotificationServiceImpl.java index 648b8bd3b..64a736987 100644 --- a/src/net/java/sip/communicator/service/notification/NotificationServiceImpl.java +++ b/src/net/java/sip/communicator/service/notification/NotificationServiceImpl.java @@ -33,20 +33,17 @@ class NotificationServiceImpl implements NotificationService { - private final Logger logger - = Logger.getLogger(NotificationServiceImpl.class); - - private final ConfigurationService configService = - NotificationServiceActivator.getConfigurationService(); - private static final String NOTIFICATIONS_PREFIX = "net.java.sip.communicator.impl.notifications"; /** - * A set of all registered event notifications. + * A list of all registered NotificationChangeListeners. */ - private final Map notifications - = new HashMap(); + private final List changeListeners + = new Vector(); + + private final ConfigurationService configService = + NotificationServiceActivator.getConfigurationService(); /** * A set of all registered event notifications. @@ -60,11 +57,8 @@ class NotificationServiceImpl private final Map handlers = new HashMap(); - /** - * A list of all registered NotificationChangeListeners. - */ - private final List changeListeners - = new Vector(); + private final Logger logger + = Logger.getLogger(NotificationServiceImpl.class); /** * Queue to cache fired notifications before all handlers are registered. @@ -72,6 +66,12 @@ class NotificationServiceImpl private Queue notificationCache = new LinkedList(); + /** + * A set of all registered event notifications. + */ + private final Map notifications + = new HashMap(); + /** * Creates an instance of NotificationServiceImpl by loading all * previously saved notifications. @@ -83,361 +83,213 @@ class NotificationServiceImpl } /** - * Creates a new EventNotification or obtains the corresponding - * existing one and registers a new action in it. + * 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 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 NotificationAction responsible for - * handling the given actionType + * @param handler The handler that executes the action. */ - public void registerNotificationForEvent( String eventType, - NotificationAction action) + public void addActionHandler(NotificationHandler handler) { - Notification notification = null; + if(handler == null) + throw new IllegalArgumentException("handler cannot be null"); - if(notifications.containsKey(eventType)) - notification = notifications.get(eventType); - else + synchronized(handlers) { - notification = new Notification(eventType); - notifications.put(eventType, notification); - - this.fireNotificationEventTypeEvent( - EVENT_TYPE_ADDED, eventType); - } - - Object existingAction = notification.addAction(action); + handlers.put(handler.getActionType(), handler); + if((handlers.size() == NUM_ACTIONS) && (notificationCache != null)) + { + for(NotificationData event : notificationCache) + fireNotification(event); - // 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); + notificationCache.clear(); + notificationCache = null; + } } - - // Save the notification through the ConfigurationService. - this.saveNotification(eventType, - action, - true, - false); } /** - * Creates a new EventNotification or obtains the corresponding - * existing one and registers a new action in it. + * Adds the given listener to the list of change listeners. * - * @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. + * @param listener the listener that we'd like to register to listen for + * changes in the event notifications stored by this service. */ - public void registerNotificationForEvent( String eventType, - String actionType, - String actionDescriptor, - String defaultMessage) + public void addNotificationChangeListener( + NotificationChangeListener listener) { - 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)) + synchronized (changeListeners) { - registerNotificationForEvent (eventType, - new CommandNotificationAction(actionDescriptor)); + changeListeners.add(listener); } } /** - * Removes the EventNotification corresponding to the given - * eventType 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. + * 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. */ - public void removeEventNotification(String eventType) + private void checkDefaultAgainstLoadedNotification + (String eventType, NotificationAction defaultAction) { - notifications.remove(eventType); - - this.fireNotificationEventTypeEvent( - EVENT_TYPE_REMOVED, eventType); + // 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); + } + } } /** - * Removes the given actionType from the list of actions registered for the - * given eventType. - * - * @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). + * Executes a notification data object on the handlers. + * @param data The notification data to act upon. */ - public void removeEventNotificationAction( String eventType, - String actionType) + private void fireNotification(NotificationData data) { - Notification notification - = notifications.get(eventType); - - if(notification == null) - return; + Notification notification = notifications.get(data.getEventType()); - NotificationAction action = notification.getAction(actionType); - - if(action == null) + if((notification == null) || !notification.isActive()) return; - notification.removeAction(actionType); + for(NotificationAction action : notification.getActions().values()) + { + String actionType = action.getActionType(); - saveNotification( - eventType, - action, - false, - false); + if(!action.isEnabled()) + continue; - fireNotificationActionTypeEvent( - ACTION_REMOVED, - eventType, - action); - } - - /** - * 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 getRegisteredEvents() - { - return Collections.unmodifiableSet( - notifications.keySet()); - } - - /** - * Returns the notification action corresponding to the given - * eventType and actionType. - * - * @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); - - if(notification == null) - return null; - - return notification.getAction(actionType); - } - - /** - * Adds the given listener 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); - } - } - - /** - * Removes the given listener 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); - } - } - - /** - * 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; - } - } - } - - /** - * 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); - } - } - - /** - * Gets a list of handler for the specified action type. - * - * @param actionType the type for which the list of handlers should be - * retrieved or null if all handlers shall be returned. - */ - public Iterable getActionHandlers(String actionType) - { - if (actionType != null) - { NotificationHandler handler = handlers.get(actionType); - Set ret; if (handler == null) - ret = Collections.emptySet(); - else - ret = Collections.singleton(handler); - return ret; - } - else - return handlers.values(); - } - - /** - * 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() || !handlers.containsKey(actionType)) continue; - NotificationHandler handler = handlers.get(actionType); if (actionType.equals(ACTION_POPUP_MESSAGE)) { - ((PopupMessageNotificationHandler) handler) - .popupMessage((PopupMessageNotificationAction) action, - data.getTitle(), data.getMessage(), - data.getIcon(), data.getTag()); + ((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, + ((LogMessageNotificationHandler) handler).logMessage( + (LogMessageNotificationAction) action, data.getMessage()); } else if (actionType.equals(ACTION_SOUND)) { SoundNotificationAction soundNotificationAction = (SoundNotificationAction) action; + if(soundNotificationAction.isSoundNotificationEnabled() - || soundNotificationAction.isSoundPlaybackEnabled() - || soundNotificationAction.isSoundPCSpeakerEnabled()) + || soundNotificationAction.isSoundPlaybackEnabled() + || soundNotificationAction.isSoundPCSpeakerEnabled()) { - ((SoundNotificationHandler) handler) - .start((SoundNotificationAction) action, data); + ((SoundNotificationHandler) handler).start( + (SoundNotificationAction) action, + data); } } else if (actionType.equals(ACTION_COMMAND)) { - ((CommandNotificationHandler) handler) - .execute( - (CommandNotificationAction)action, - data.getExtra()); + @SuppressWarnings("unchecked") + Map cmdargs + = (Map) + data.getExtra( + NotificationData + .COMMAND_NOTIFICATION_HANDLER_CMDARGS_EXTRA); + + ((CommandNotificationHandler) handler).execute( + (CommandNotificationAction) action, + cmdargs); } } } /** - * 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.. + * If there is a registered event notification of the given + * eventType 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 void stopNotification(NotificationData data) + public NotificationData fireNotification(String eventType) { - Iterable 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); - } - } + return fireNotification(eventType, null, null, null); } /** @@ -461,15 +313,9 @@ public NotificationData fireNotification( String eventType, String title, String message, - byte[] icon, - Object tag) + byte[] icon) { - return fireNotification(eventType, - title, - message, - null, - icon, - tag); + return fireNotification(eventType, title, message, icon, null); } /** @@ -482,28 +328,30 @@ public NotificationData fireNotification( * @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 extra the extra data to pass (especially for Command execution) * @param icon the icon to show in the notification if and where appropriate - * @param tag additional info to be used by the notification handler + * @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 NotificationData + * XXX_EXTRA 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, - Map extra, - byte[] icon, - Object tag) + String eventType, + String title, + String message, + byte[] icon, + Map extras) { Notification notification = notifications.get(eventType); - if(notification == null || !notification.isActive()) + + if((notification == null) || !notification.isActive()) return null; - NotificationData data = new NotificationData(eventType, title, - message, extra, icon, tag); + NotificationData data + = new NotificationData(eventType, title, message, icon, extras); //cache the notification when the handlers are not yet ready if (notificationCache != null) @@ -515,67 +363,156 @@ public NotificationData fireNotification( } /** - * If there is a registered event notification of the given - * eventType 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. + * Notifies all registered NotificationChangeListeners that a + * NotificationActionTypeEvent has occurred. * - * @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. + * @param eventType the type of the event, which is one of ACTION_XXX + * constants declared in the NotificationActionTypeEvent class. + * @param sourceEventType the eventType, which is the parent of the + * action + * @param action the notification action */ - public NotificationData fireNotification(String eventType) + private void fireNotificationActionTypeEvent( + String eventType, + String sourceEventType, + NotificationAction action) { - return this.fireNotification(eventType, null, null, null, null, null); + 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); + } + } } /** - * Saves the event notification given by these parameters through the - * ConfigurationService. + * Notifies all registered NotificationChangeListeners that a + * NotificationEventTypeEvent has occurred. * - * @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 + * @param eventType the type of the event, which is one of EVENT_TYPE_XXX + * constants declared in the NotificationEventTypeEvent class. + * @param sourceEventType the eventType, for which this event is + * about */ - private void saveNotification( String eventType, - NotificationAction action, - boolean isActive, - boolean isDefault) + private void fireNotificationEventTypeEvent(String eventType, + String sourceEventType) { - String eventTypeNodeName = null; - String actionTypeNodeName = null; - - List eventTypes = configService - .getPropertyNamesByPrefix(NOTIFICATIONS_PREFIX, true); + if (logger.isDebugEnabled()) + logger.debug("Dispatching NotificationEventType Change. Listeners=" + + changeListeners.size() + + " evt=" + eventType); - for (String eventTypeRootPropName : eventTypes) - { - String eType = configService.getString(eventTypeRootPropName); - if(eType.equals(eventType)) - eventTypeNodeName = eventTypeRootPropName; - } + NotificationEventTypeEvent event + = new NotificationEventTypeEvent(this, eventType, sourceEventType); - // If we didn't find the given event type in the configuration we save - // it here. - if(eventTypeNodeName == null) + for (NotificationChangeListener listener : changeListeners) { - eventTypeNodeName = NOTIFICATIONS_PREFIX - + ".eventType" - + Long.toString(System.currentTimeMillis()); - - configService.setProperty(eventTypeNodeName, eventType); + if (eventType.equals(EVENT_TYPE_ADDED)) + { + listener.eventTypeAdded(event); + } + else if (eventType.equals(EVENT_TYPE_REMOVED)) + { + listener.eventTypeRemoved(event); + } } + } - // if we set active/inactive for the whole event notification - if(action == null) + /** + * Gets a list of handler for the specified action type. + * + * @param actionType the type for which the list of handlers should be + * retrieved or null if all handlers shall be returned. + */ + public Iterable getActionHandlers(String actionType) + { + if (actionType != null) { - configService.setProperty( - eventTypeNodeName + ".active", - Boolean.toString(isActive)); - return; + NotificationHandler handler = handlers.get(actionType); + Set 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 + * eventType and actionType. + * + * @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 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. @@ -591,81 +528,102 @@ private void saveNotification( String eventType, actionTypeNodeName = actionTypeRootPropName; } - Map configProperties = new HashMap(); - - // If we didn't find the given actionType in the configuration we save - // it here. + // If we didn't find the given actionType in the configuration + // there is no need to further check if(actionTypeNodeName == null) - { - actionTypeNodeName = actionPrefix - + ".actionType" - + Long.toString(System.currentTimeMillis()); + throw new IllegalArgumentException("Missing action type node"); - configProperties.put(actionTypeNodeName, action.getActionType()); - } + return + (String) + configService.getProperty(actionTypeNodeName + "." + property); + } - if(action instanceof SoundNotificationAction) - { - SoundNotificationAction soundAction - = (SoundNotificationAction) action; + /** + * 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 getRegisteredEvents() + { + return Collections.unmodifiableSet( + notifications.keySet()); + } - configProperties.put( - actionTypeNodeName + ".soundFileDescriptor", - soundAction.getDescriptor()); + /** + * Finds the EventNotification corresponding to the given + * eventType 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 true if actions for the specified eventType + * are activated, false - otherwise. If the given + * eventType is not contained in the list of registered event + * types - returns false. + */ + public boolean isActive(String eventType) + { + Notification eventNotification + = notifications.get(eventType); - configProperties.put( - actionTypeNodeName + ".loopInterval", - soundAction.getLoopInterval()); + if(eventNotification == null) + return false; - configProperties.put( - actionTypeNodeName + ".isSoundNotificationEnabled", - soundAction.isSoundNotificationEnabled()); + return eventNotification.isActive(); + } - configProperties.put( - actionTypeNodeName + ".isSoundPlaybackEnabled", - soundAction.isSoundPlaybackEnabled()); + private boolean isDefault(String eventType, String actionType) + { + List eventTypes = configService + .getPropertyNamesByPrefix(NOTIFICATIONS_PREFIX, true); - configProperties.put( - actionTypeNodeName + ".isSoundPCSpeakerEnabled", - soundAction.isSoundPCSpeakerEnabled()); - } - else if(action instanceof PopupMessageNotificationAction) + for (String eventTypeRootPropName : eventTypes) { - PopupMessageNotificationAction messageAction - = (PopupMessageNotificationAction) action; + String eType + = configService.getString(eventTypeRootPropName); - configProperties.put( - actionTypeNodeName + ".defaultMessage", - messageAction.getDefaultMessage()); - } - else if(action instanceof LogMessageNotificationAction) - { - LogMessageNotificationAction logMessageAction - = (LogMessageNotificationAction) action; + if(!eType.equals(eventType)) + continue; - configProperties.put( - actionTypeNodeName + ".logType", - logMessageAction.getLogType()); - } - else if(action instanceof CommandNotificationAction) - { - CommandNotificationAction commandAction - = (CommandNotificationAction) action; + List actions = configService + .getPropertyNamesByPrefix( + eventTypeRootPropName + ".actions", true); - configProperties.put( - actionTypeNodeName + ".commandDescriptor", - commandAction.getDescriptor()); - } + for (String actionPropName : actions) + { + String aType + = configService.getString(actionPropName); - configProperties.put( - actionTypeNodeName + ".enabled", - Boolean.toString(isActive)); + if(!aType.equals(actionType)) + continue; - configProperties.put( - actionTypeNodeName + ".default", - Boolean.toString(isDefault)); + Object isDefaultdObj = + configService.getProperty(actionPropName + ".default"); - configService.setProperties(configProperties); + // 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); } /** @@ -766,175 +724,9 @@ else if(actionType.equals(ACTION_COMMAND)) } } - 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); - } - /** - * Finds the EventNotification corresponding to the given - * eventType 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 eventType. - */ - public void setActive(String eventType, boolean isActive) - { - Notification eventNotification - = notifications.get(eventType); - - if(eventNotification == null) - return; - - eventNotification.setActive(isActive); - saveNotification(eventType, null, isActive, false); - } - - /** - * Finds the EventNotification corresponding to the given - * eventType 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 true if actions for the specified eventType - * are activated, false - otherwise. If the given - * eventType is not contained in the list of registered event - * types - returns false. - */ - public boolean isActive(String eventType) - { - Notification eventNotification - = notifications.get(eventType); - - if(eventNotification == null) - return false; - - return eventNotification.isActive(); - } - - /** - * Notifies all registered NotificationChangeListeners that a - * NotificationEventTypeEvent has occurred. - * - * @param eventType the type of the event, which is one of EVENT_TYPE_XXX - * constants declared in the NotificationEventTypeEvent class. - * @param sourceEventType the eventType, 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); - } - } - } - - /** - * Notifies all registered NotificationChangeListeners that a - * NotificationActionTypeEvent has occurred. - * - * @param eventType the type of the event, which is one of ACTION_XXX - * constants declared in the NotificationActionTypeEvent class. - * @param sourceEventType the eventType, 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); - } - } - } - - private boolean isDefault(String eventType, String actionType) - { - List eventTypes = configService - .getPropertyNamesByPrefix(NOTIFICATIONS_PREFIX, true); - - for (String eventTypeRootPropName : eventTypes) - { - String eType - = configService.getString(eventTypeRootPropName); - - if(!eType.equals(eventType)) - continue; - - List 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; - } - - /** - * Creates a new default EventNotification or obtains the - * corresponding existing one and registers a new action in it. + * Creates a new default EventNotification 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. @@ -1114,105 +906,230 @@ else if (actionType.equals(ACTION_COMMAND)) } /** - * 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. + * Creates a new EventNotification 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 NotificationAction responsible for + * handling the given actionType */ - private void checkDefaultAgainstLoadedNotification - (String eventType, NotificationAction defaultAction) + public void registerNotificationForEvent( String eventType, + NotificationAction action) { - // checking for new sound action properties - if(defaultAction instanceof SoundNotificationAction) + Notification notification = null; + + if(notifications.containsKey(eventType)) + notification = notifications.get(eventType); + else { - SoundNotificationAction soundDefaultAction - = (SoundNotificationAction)defaultAction; - SoundNotificationAction soundAction = (SoundNotificationAction) - getEventNotificationAction(eventType, ACTION_SOUND); + notification = new Notification(eventType); + notifications.put(eventType, notification); - boolean isSoundNotificationEnabledPropExist - = getNotificationActionProperty( - eventType, - defaultAction, - "isSoundNotificationEnabled") != null; + this.fireNotificationEventTypeEvent( + EVENT_TYPE_ADDED, eventType); + } - if(!isSoundNotificationEnabledPropExist) - { - soundAction.setSoundNotificationEnabled( - soundDefaultAction.isSoundNotificationEnabled()); - } + Object existingAction = notification.addAction(action); - boolean isSoundPlaybackEnabledPropExist - = getNotificationActionProperty( - eventType, - defaultAction, - "isSoundPlaybackEnabled") != null; + // 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); + } - if(!isSoundPlaybackEnabledPropExist) - { - soundAction.setSoundPlaybackEnabled( - soundDefaultAction.isSoundPlaybackEnabled()); - } + // Save the notification through the ConfigurationService. + this.saveNotification(eventType, + action, + true, + false); + } - boolean isSoundPCSpeakerEnabledPropExist - = getNotificationActionProperty( - eventType, - defaultAction, - "isSoundPCSpeakerEnabled") != null; + /** + * Creates a new EventNotification 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(!isSoundPCSpeakerEnabledPropExist) - { - soundAction.setSoundPCSpeakerEnabled( - soundDefaultAction.isSoundPCSpeakerEnabled()); - } + 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)); + } + } - boolean fixDialingLoop = false; + /** + * 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"); - // 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; - } + synchronized(handlers) + { + handlers.remove(actionType); + } + } - 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); - } + /** + * Removes the EventNotification corresponding to the given + * eventType 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 eventType. + * + * @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 listener 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); } } /** - * 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. + * Deletes all registered events and actions + * and registers and saves the default events as current. */ - private String getNotificationActionProperty( - String eventType, - NotificationAction action, - String property) - throws IllegalArgumentException + public void restoreDefaults() + { + for (String eventType : new Vector(notifications.keySet())) + { + Notification notification = notifications.get(eventType); + + for (String actionType + : new Vector(notification.getActions().keySet())) + removeEventNotificationAction(eventType, actionType); + + removeEventNotification(eventType); + } + + for (Map.Entry 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 + * ConfigurationService. + * + * @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; @@ -1227,11 +1144,24 @@ private String getNotificationActionProperty( eventTypeNodeName = eventTypeRootPropName; } - // If we didn't find the given event type in the configuration - // there is not need to further check + // If we didn't find the given event type in the configuration we save + // it here. if(eventTypeNodeName == null) { - throw new IllegalArgumentException("Missing event type node"); + 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. @@ -1249,43 +1179,121 @@ private String getNotificationActionProperty( Map configProperties = new HashMap(); - // If we didn't find the given actionType in the configuration - // there is no need to further check + // If we didn't find the given actionType in the configuration we save + // it here. if(actionTypeNodeName == null) { - throw new IllegalArgumentException("Missing action type node"); + actionTypeNodeName = actionPrefix + + ".actionType" + + Long.toString(System.currentTimeMillis()); + + configProperties.put(actionTypeNodeName, action.getActionType()); } - return - (String)configService - .getProperty(actionTypeNodeName + "." + property); + 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()); + } + 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()); + } + + configProperties.put( + actionTypeNodeName + ".enabled", + Boolean.toString(isActive)); + + configProperties.put( + actionTypeNodeName + ".default", + Boolean.toString(isDefault)); + + configService.setProperties(configProperties); } /** - * Deletes all registered events and actions - * and registers and saves the default events as current. + * Finds the EventNotification corresponding to the given + * eventType 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 eventType. */ - public void restoreDefaults() + public void setActive(String eventType, boolean isActive) { - for (String eventType : new Vector(notifications.keySet())) - { - Notification notification = notifications.get(eventType); + Notification eventNotification + = notifications.get(eventType); - for (String actionType - : new Vector(notification.getActions().keySet())) - removeEventNotificationAction(eventType, actionType); + if(eventNotification == null) + return; - removeEventNotification(eventType); - } + eventNotification.setActive(isActive); + saveNotification(eventType, null, isActive, false); + } - for (Map.Entry entry - : defaultNotifications.entrySet()) - { - String eventType = entry.getKey(); - Notification notification = entry.getValue(); + /** + * 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 soundHandlers + = getActionHandlers(NotificationAction.ACTION_SOUND); - for (NotificationAction action : notification.getActions().values()) - registerNotificationForEvent(eventType, action); + // 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); + } } } } diff --git a/src/net/java/sip/communicator/service/notification/PopupMessageNotificationHandler.java b/src/net/java/sip/communicator/service/notification/PopupMessageNotificationHandler.java index e8b57161f..d9cdfd018 100644 --- a/src/net/java/sip/communicator/service/notification/PopupMessageNotificationHandler.java +++ b/src/net/java/sip/communicator/service/notification/PopupMessageNotificationHandler.java @@ -29,11 +29,12 @@ public interface PopupMessageNotificationHandler * appropriate * @param tag additional info to be used by the notification handler */ - public void popupMessage(PopupMessageNotificationAction action, - String title, - String message, - byte[] icon, - Object tag); + public void popupMessage( + PopupMessageNotificationAction action, + String title, + String message, + byte[] icon, + Object tag); /** * Adds a listener for SystrayPopupMessageEvents posted when user diff --git a/src/net/java/sip/communicator/util/dns/ConfigurableDnssecResolver.java b/src/net/java/sip/communicator/util/dns/ConfigurableDnssecResolver.java index 0a5f4fb8a..cae92fcd4 100644 --- a/src/net/java/sip/communicator/util/dns/ConfigurableDnssecResolver.java +++ b/src/net/java/sip/communicator/util/dns/ConfigurableDnssecResolver.java @@ -142,9 +142,10 @@ protected void validateMessage(SecureMessage msg) || last.before(new Date(new Date().getTime() - 1000*60*5))) { DnsUtilActivator.getNotificationService().fireNotification( - EVENT_TYPE, - R.getI18NString("util.dns.INSECURE_ANSWER_TITLE"), - text, null, null); + EVENT_TYPE, + R.getI18NString("util.dns.INSECURE_ANSWER_TITLE"), + text, + null); lastNotifications.put(text, new Date()); } throw new DnssecRuntimeException(text); diff --git a/test/net/java/sip/communicator/slick/popupmessagehandler/TestPopupMessageHandler.java b/test/net/java/sip/communicator/slick/popupmessagehandler/TestPopupMessageHandler.java index babec92d8..01b6f3b00 100644 --- a/test/net/java/sip/communicator/slick/popupmessagehandler/TestPopupMessageHandler.java +++ b/test/net/java/sip/communicator/slick/popupmessagehandler/TestPopupMessageHandler.java @@ -10,11 +10,9 @@ import net.java.sip.communicator.service.notification.*; import net.java.sip.communicator.service.systray.*; import net.java.sip.communicator.service.systray.event.*; -import net.java.sip.communicator.util.*; import org.osgi.framework.*; - /** * Test suite for the popup message handler interface. * @author Symphorien Wanko @@ -22,10 +20,6 @@ public class TestPopupMessageHandler extends TestCase { - /** Logger for this class */ - private static final Logger logger - = Logger.getLogger(TestPopupMessageHandler.class); - /** * the SystrayService reference we will get from bundle * context to register ours handlers @@ -121,7 +115,6 @@ public void testNotificationHandling() { serviceReference = bc.getServiceReference( NotificationService.class.getName()); - notificationService = (NotificationService) bc.getService(serviceReference); @@ -129,7 +122,6 @@ public void testNotificationHandling() NotificationAction.ACTION_POPUP_MESSAGE, messageStart, messageStart, - null, null); }