diff options
author | Lyubomir Marinov <lyubomir.marinov@jitsi.org> | 2012-10-18 14:06:40 +0000 |
---|---|---|
committer | Lyubomir Marinov <lyubomir.marinov@jitsi.org> | 2012-10-18 14:06:40 +0000 |
commit | 98fef59119c22bcb8e09fad6a4a1d787ea84b665 (patch) | |
tree | 6e16dff777deaa75755755f7ad449edf51fff836 | |
parent | 93aeb79acfee9f44297acc7e2e6ca1abb97d19dd (diff) | |
download | jitsi-98fef59119c22bcb8e09fad6a4a1d787ea84b665.zip jitsi-98fef59119c22bcb8e09fad6a4a1d787ea84b665.tar.gz jitsi-98fef59119c22bcb8e09fad6a4a1d787ea84b665.tar.bz2 |
Works on preventing sound notifications from playing forever.
14 files changed, 2057 insertions, 1978 deletions
diff --git a/lib/installer-exclude/libjitsi.jar b/lib/installer-exclude/libjitsi.jar Binary files differindex c1091d4..6feb19a 100644 --- a/lib/installer-exclude/libjitsi.jar +++ b/lib/installer-exclude/libjitsi.jar diff --git a/src/net/java/sip/communicator/impl/neomedia/NeomediaActivator.java b/src/net/java/sip/communicator/impl/neomedia/NeomediaActivator.java index edf966b..bf8b5b6 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.*; @@ -147,11 +139,6 @@ public class NeomediaActivator deviceConfigurationPropertyChangeListener; /** - * Audio configuration dialog. - */ - private static SIPCommDialog audioConfigDialog = null; - - /** * A {@link MediaConfigurationService} instance. */ private static MediaConfigurationImpl mediaConfiguration; @@ -553,7 +540,7 @@ public class NeomediaActivator } /** - * 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 class NeomediaActivator { NotificationService notificationService = getNotificationService(); + if(notificationService != null) { // Registers only once to the popup message notification @@ -573,9 +561,15 @@ public class NeomediaActivator isRegisteredToPopupMessageListener = true; managePopupMessageListenerRegistration(true); } + // Fires the popup notification. ResourceManagementService resources = NeomediaActivator.getResources(); + Map<String,Object> extras = new HashMap<String,Object>(); + + extras.put( + NotificationData.POPUP_MESSAGE_HANDLER_TAG_EXTRA, + this); notificationService.fireNotification( DEVICE_CONFIGURATION_HAS_CHANGED, resources.getI18NString( @@ -585,7 +579,7 @@ public class NeomediaActivator "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 3395032..c15fb53 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 class PopupMessageNotificationHandlerImpl 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 fad7e2b..c3e4ab2 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 @@ package net.java.sip.communicator.impl.notification; 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 @@ import org.jitsi.util.*; public class SoundNotificationHandlerImpl implements SoundNotificationHandler { - WeakHashMap<SCAudioClip, NotificationData> playedClips = - new WeakHashMap<SCAudioClip, NotificationData>(); - /** - * If the sound is currently disabled. + * The indicator which determines whether this + * <tt>SoundNotificationHandler</tt> is currently muted i.e. the sounds are + * off. */ - private boolean isMute; + private boolean mute; + + private Map<SCAudioClip, NotificationData> playedClips + = new WeakHashMap<SCAudioClip, NotificationData>(); /** * {@inheritDoc} @@ -40,55 +43,13 @@ public class SoundNotificationHandlerImpl } /** - * Plays the sound given by the containing <tt>soundFileDescriptor</tt>. 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 class SoundNotificationHandlerImpl * 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 @@ public class SoundNotificationHandlerImpl // 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<Boolean> loopCondition + = (Callable<Boolean>) + data.getExtra( + NotificationData + .SOUND_NOTIFICATION_HANDLER_LOOP_CONDITION_EXTRA); - for (Map.Entry<SCAudioClip, NotificationData> 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 class SoundNotificationHandlerImpl */ public void setMute(boolean isMute) { - this.isMute = isMute; + this.mute = isMute; if(isMute) { @@ -189,182 +143,117 @@ public class SoundNotificationHandlerImpl } /** - * 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 <tt>soundFileDescriptor</tt>. 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<SCAudioClip, NotificationData> 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 <tt>PCSpeakerClip</tt> 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 <tt>true</tt> if the playback was successful; - * otherwise, <tt>false</tt> + * @return <tt>true</tt> if the playback was successful; otherwise, + * <tt>false</tt> */ - 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 <tt>SCAudioClip</tt>s 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 e5db2e6..1c1d162 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 @@ public class LoggingConfigForm 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 @@ public class LoggingConfigForm 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 4aa9cb1..d2022e3 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 @@ package net.java.sip.communicator.plugin.notificationwiring; import java.awt.image.*; import java.net.*; import java.util.*; +import java.util.concurrent.*; import javax.imageio.*; @@ -22,61 +23,36 @@ import net.java.sip.communicator.util.*; 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<ImageID, BufferedImage> loadedImages = - new Hashtable<ImageID, BufferedImage>(); - - /** - * Pseudo timer used to delay multiple typings notifications before - * receiving the message. - * - * Time to live : 1 minute - */ - private Map<Contact,Long> proactiveTimer = new HashMap<Contact, Long>(); - - /** - * 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<Call, NotificationData> callNotifications = - new WeakHashMap<Call, NotificationData>(); + public static final String BUSY_CALL = "BusyCall"; /** * Default event type for call been saved using a recorder. @@ -84,11 +60,6 @@ public class NotificationManager 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. */ public static final String CALL_SECURITY_ERROR = "CallSecurityError"; @@ -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 <tt>BufferedImage</tt> instances which we have already + * loaded by <tt>ImageID</tt> 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<ImageID, BufferedImage> images + = new Hashtable<ImageID, BufferedImage>(); /** - * 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 <tt>Logger</tt> used by the <tt>NotificationManager</tt> 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 + * <tt>NotificationService</tt>. + * + * @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,116 +152,232 @@ public class NotificationManager if(notificationService == null) return; - // Register incoming message notifications. - notificationService.registerDefaultNotificationForEvent( - INCOMING_MESSAGE, - NotificationAction.ACTION_POPUP_MESSAGE, - null, - null); + NotificationAction popupActionHandler = null; + UIService uiService = NotificationWiringActivator.getUIService(); - notificationService.registerDefaultNotificationForEvent( - INCOMING_MESSAGE, - new SoundNotificationAction( - SoundProperties.INCOMING_MESSAGE, -1, true, false, false)); + Chat chatPanel = null; + byte[] contactIcon = null; + if (chatContact instanceof Contact) + { + Contact contact = (Contact) chatContact; - // Register incoming call notifications. - notificationService.registerDefaultNotificationForEvent( - INCOMING_CALL, - NotificationAction.ACTION_POPUP_MESSAGE, - null, - null); + if(uiService != null) + chatPanel = uiService.getChat(contact); - SoundNotificationAction inCallSoundHandler - = new SoundNotificationAction( - SoundProperties.INCOMING_CALL, 2000, true, true, true); + contactIcon = contact.getImage(); + if(contactIcon == null) + { + contactIcon = + ImageUtils.toByteArray(getImage(DEFAULT_USER_PHOTO)); + } + } + else if (chatContact instanceof ChatRoom) + { + ChatRoom chatRoom = (ChatRoom) chatContact; - notificationService.registerDefaultNotificationForEvent( - INCOMING_CALL, - inCallSoundHandler); + // For system rooms we don't want to send notification events. + if (chatRoom.isSystem()) + return; - // Register outgoing call notifications. - SoundNotificationAction outCallSoundHandler - = new SoundNotificationAction( - SoundProperties.OUTGOING_CALL, 3000, false, true, false); + if(uiService != null) + chatPanel = uiService.getChat(chatRoom); + } - notificationService.registerDefaultNotificationForEvent( - OUTGOING_CALL, - outCallSoundHandler); + if (chatPanel != null) + { + if (eventType.equals(INCOMING_MESSAGE) + && chatPanel.isChatFocused()) + { + popupActionHandler = notificationService + .getEventNotificationAction(eventType, + NotificationAction.ACTION_POPUP_MESSAGE); - // Register busy call notifications. - SoundNotificationAction busyCallSoundHandler - = new SoundNotificationAction(SoundProperties.BUSY, 1, - false, true, false); + popupActionHandler.setEnabled(false); + } + } - notificationService.registerDefaultNotificationForEvent( - BUSY_CALL, - busyCallSoundHandler); + Map<String,Object> extras = new HashMap<String,Object>(); - // Register dial notifications. - SoundNotificationAction dialSoundHandler - = new SoundNotificationAction( - SoundProperties.DIALING, -1, false, true, false); + extras.put( + NotificationData.POPUP_MESSAGE_HANDLER_TAG_EXTRA, + chatContact); + notificationService.fireNotification( + eventType, + messageTitle, + message, + contactIcon, + extras); - notificationService.registerDefaultNotificationForEvent( - DIALING, - dialSoundHandler); + if(popupActionHandler != null) + popupActionHandler.setEnabled(true); + } - // Register the hangup sound notification. - SoundNotificationAction hangupSoundHandler - = new SoundNotificationAction( - SoundProperties.HANG_UP, -1, false, true, false); + /** + * Fires a notification for the given event type through the + * <tt>NotificationService</tt>. The event type is one of the static + * constants defined in the <tt>NotificationManager</tt> class. + * <p> + * <b>Note</b>: 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 <tt>NotificationService</tt> so that sounds are played once + * only. + * </p> + * + * @param eventType the event type for which we want to fire a notification + */ + private static void fireNotification(String eventType) + { + NotificationService notificationService + = NotificationWiringActivator.getNotificationService(); - notificationService.registerDefaultNotificationForEvent( - HANG_UP, - hangupSoundHandler); + if (notificationService != null) + notificationService.fireNotification(eventType); + } - // Register proactive notifications. - notificationService.registerDefaultNotificationForEvent( - PROACTIVE_NOTIFICATION, - NotificationAction.ACTION_POPUP_MESSAGE, - null, - null); + /** + * Fires a notification for the given event type through the + * <tt>NotificationService</tt>. The event type is one of the static + * constants defined in the <tt>NotificationManager</tt> class. + * + * @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. + */ + private static NotificationData fireNotification( + String eventType, + Callable<Boolean> loopCondition) + { + return fireNotification(eventType, null, null, null, loopCondition); + } - // Register warning message notifications. - notificationService.registerDefaultNotificationForEvent( - SECURITY_MESSAGE, - NotificationAction.ACTION_POPUP_MESSAGE, - null, - null); + /** + * Fires a notification through the <tt>NotificationService</tt> with a + * specific event type, a specific message title and a specific message. + * <p> + * <b>Note</b>: 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 <tt>NotificationService</tt> so that sounds are played once + * only. + * </p> + * + * @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(); - // 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)); + if (notificationService != null) + { + notificationService.fireNotification( + eventType, + messageTitle, + message, + null); + } + } - // 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)); + /** + * Fires a message notification for the given event type through the + * <tt>NotificationService</tt>. + * + * @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 <tt>cmdargs</tt> 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<String,String> cmdargs, + Callable<Boolean> loopCondition) + { + NotificationService notificationService + = NotificationWiringActivator.getNotificationService(); - // Register sound notification for incoming files. - notificationService.registerDefaultNotificationForEvent( - INCOMING_FILE, - NotificationAction.ACTION_POPUP_MESSAGE, - null, - null); + if (notificationService == null) + return null; + else + { + Map<String,Object> extras = new HashMap<String,Object>(); - notificationService.registerDefaultNotificationForEvent( - INCOMING_FILE, - new SoundNotificationAction( - SoundProperties.INCOMING_FILE, -1, - true, false, false)); + 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); + } + } - // Register notification for saved calls. - notificationService.registerDefaultNotificationForEvent( - CALL_SAVED, - NotificationAction.ACTION_POPUP_MESSAGE, - null, - null); + /** + * 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; } /** @@ -364,6 +462,226 @@ public class NotificationManager } /** + * Determines whether a specific <code>ChatRoom</code> 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 <code>ChatRoom</code> is + * obviously not private and (2) a <code>ChatRoom</code> is private if it + * has only one <code>ChatRoomMember</code> who is not the local user. + * + * @param chatRoom + * the <code>ChatRoom</code> to be determined as private or not + * @return <tt>true</tt> if the specified <code>ChatRoom</code> is private; + * otherwise, <tt>false</tt> + */ + private static boolean isPrivate(ChatRoom chatRoom) + { + if (!chatRoom.isSystem() + && chatRoom.isJoined() + && (chatRoom.getMembersCount() == 1)) + { + String nickname = chatRoom.getUserNickname(); + + if (nickname != null) + { + for (ChatRoomMember member : chatRoom.getMembers()) + if (nickname.equals(member.getName())) + return false; + return true; + } + } + return false; + } + + /** + * 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) + notificationService.stopNotification(data); + } + + /** + * Stores notification references to stop them if a notification has expired + * (e.g. to stop the dialing sound). + */ + private final Map<Call, NotificationData> callNotifications + = new WeakHashMap<Call, NotificationData>(); + + /** + * The pseudo timer which is used to delay multiple typing notifications + * before receiving the message. + */ + private final Map<Contact, Long> proactiveTimer + = new HashMap<Contact, Long>(); + + /** + * Implements CallListener.callEnded. Stops sounds that are playing at + * the moment if there're any. + * @param event the <tt>CallEvent</tt> + */ + 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 <tt>CallChangeListener.callPeerAdded</tt> method. + * @param evt the <tt>CallPeerEvent</tt> that notifies us for the change + */ + public void callPeerAdded(CallPeerEvent evt) + { + CallPeer peer = evt.getSourceCallPeer(); + + if(peer == null) + return; + + peer.addCallPeerListener(this); + peer.addCallPeerSecurityListener(this); + peer.addCallPeerConferenceListener(this); + } + + /** + * Implements the <tt>CallChangeListener.callPeerRemoved</tt> method. + * @param evt the <tt>CallPeerEvent</tt> that has been triggered + */ + public void callPeerRemoved(CallPeerEvent evt) + { + CallPeer peer = evt.getSourceCallPeer(); + + if(peer == null) + return; + + peer.removeCallPeerListener(this); + peer.removeCallPeerSecurityListener(this); + peer.addCallPeerConferenceListener(this); + } + + /** + * {@inheritDoc} + * + * Not used. + */ + public void callStateChanged(CallChangeEvent ev) {} + + /** + * {@inheritDoc} + * + * Not used. + */ + public void conferenceFocusChanged(CallPeerConferenceEvent ev) {} + + /** + * Indicates that the given conference member has been added to the given + * peer. + * + * @param conferenceEvent the event + */ + public void conferenceMemberAdded(CallPeerConferenceEvent conferenceEvent) + { + try + { + CallPeer peer + = conferenceEvent + .getConferenceMember() + .getConferenceFocusCallPeer(); + + if(peer.getConferenceMemberCount() > 0) + { + CallPeerSecurityStatusEvent securityEvent + = peer.getCurrentSecuritySettings(); + + 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 <tt>FileTransferRequestEvent</tt> + * @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 <tt>ProtocolProviderService</tt> @@ -517,245 +835,105 @@ public class NotificationManager } /** - * Implements the <tt>ServiceListener</tt> method. Verifies whether the - * passed event concerns a <tt>ProtocolProviderService</tt> and adds the - * corresponding listeners. - * - * @param event The <tt>ServiceEvent</tt> object. - */ - public void serviceChanged(ServiceEvent event) - { - ServiceReference serviceRef = event.getServiceReference(); - - // if the event is caused by a bundle being stopped, we don't want to - // know - if (serviceRef.getBundle().getState() == Bundle.STOPPING) - { - return; - } - - Object service = - NotificationWiringActivator.bundleContext.getService(serviceRef); - - // we don't care if the source service is not a protocol provider - if (!(service instanceof ProtocolProviderService)) - { - return; - } - - switch (event.getType()) - { - case ServiceEvent.REGISTERED: - this.handleProviderAdded((ProtocolProviderService) service); - break; - case ServiceEvent.UNREGISTERING: - this.handleProviderRemoved((ProtocolProviderService) service); - break; - } - } - - /** - * Fires a message notification for the given event type through the - * <tt>NotificationService</tt>. - * - * @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. - */ - public static NotificationData fireNotification(String eventType, - String messageTitle, - String message) - { - NotificationService notificationService - = NotificationWiringActivator.getNotificationService(); - - if(notificationService == null) - return null; - - return notificationService.fireNotification( eventType, - messageTitle, - message, - null, - null); - } - - /** - * Fires a message notification for the given event type through the - * <tt>NotificationService</tt>. - * - * @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. - */ - public static NotificationData fireNotification(String eventType, - String messageTitle, - String message, - Map<String,String> extra) - { - NotificationService notificationService - = NotificationWiringActivator.getNotificationService(); - - if(notificationService == null) - return null; - - return notificationService.fireNotification(eventType, - messageTitle, - message, - extra, - null, - null); - } - - /** - * Fires a chat message notification for the given event type through the - * <tt>NotificationService</tt>. + * 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 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 ev the <tt>CallEvent</tt> */ - public static void fireChatNotification(Object chatContact, - String eventType, - String messageTitle, - String message) + public void incomingCallReceived(CallEvent ev) { - NotificationService notificationService - = NotificationWiringActivator.getNotificationService(); - - if(notificationService == null) - return; - - 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) + try { - ChatRoom chatRoom = (ChatRoom) chatContact; + final Call call = ev.getSourceCall(); + CallPeer peer = call.getCallPeers().next(); + Map<String,String> peerInfo = new HashMap<String, String>(); + 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<Boolean>() + { + public Boolean call() + { + /* + * INCOMING_CALL should be played for a Call + * only while there is a CallPeer in the + * INCOMING_CALL state. + */ + Iterator<? extends CallPeer> 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); - // For system rooms we don't want to send notification events. - if (chatRoom.isSystem()) - return; + call.addCallChangeListener(this); - if(uiService != null) - chatPanel = uiService.getChat(chatRoom); + peer.addCallPeerListener(this); + peer.addCallPeerSecurityListener(this); + peer.addCallPeerConferenceListener(this); } - - if (chatPanel != null) + catch(Throwable t) { - if (eventType.equals(INCOMING_MESSAGE) - && chatPanel.isChatFocused()) + if (t instanceof ThreadDeath) + throw (ThreadDeath) t; + else { - popupActionHandler = notificationService - .getEventNotificationAction(eventType, - NotificationAction.ACTION_POPUP_MESSAGE); - - popupActionHandler.setEnabled(false); + logger.error( + "An error occurred while trying to notify" + + " about an incoming call", + t); } } - - notificationService.fireNotification( eventType, - messageTitle, - message, - null, - contactIcon, - chatContact); - - if(popupActionHandler != null) - popupActionHandler.setEnabled(true); - } - - /** - * Fires a notification for the given event type through the - * <tt>NotificationService</tt>. 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. + * Initialize, register default notifications and start listening for + * new protocols or removed one and find any that are already registered. */ - public static void stopSound(NotificationData data) + void init() { - NotificationService notificationService - = NotificationWiringActivator.getNotificationService(); - - if(notificationService == null) - return; - - notificationService.stopNotification(data); - } + registerDefaultNotifications(); - /** - * 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; + // listens for new protocols + NotificationWiringActivator.bundleContext.addServiceListener(this); - if (loadedImages.containsKey(imageID)) - { - image = loadedImages.get(imageID); - } - else + // enumerate currently registered protocols + for(ProtocolProviderService pp : getProtocolProviders()) { - 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); - } - } + handleProviderAdded(pp); } - return image; + NotificationWiringActivator.getMediaService().addRecorderListener(this); } /** @@ -793,268 +971,238 @@ public class NotificationManager } /** - * Determines whether a specific <code>ChatRoom</code> 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 <code>ChatRoom</code> is - * obviously not private and (2) a <code>ChatRoom</code> is private if it - * has only one <code>ChatRoomMember</code> who is not the local user. + * Implements the + * <tt>LocalUserAdHocChatRoomPresenceListener.localUserPresenceChanged</tt> + * method * - * @param chatRoom - * the <code>ChatRoom</code> to be determined as private or not - * @return <tt>true</tt> if the specified <code>ChatRoom</code> is private; - * otherwise, <tt>false</tt> + * @param evt the <tt>LocalUserAdHocChatRoomPresenceChangeEvent</tt> 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 <tt>MessageReceivedEvent</tt> containing - * details on the received message + * Implements the + * <tt>LocalUserChatRoomPresenceListener.localUserPresenceChanged</tt> + * method. + * @param evt the <tt>LocalUserChatRoomPresenceChangeEvent</tt> 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 <tt>MessageDeliveredEvent</tt> 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 <tt>MessageDeliveryFailedEvent</tt> 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 <tt>FileTransferRequestEvent</tt> - * @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 <tt>FileTransferCreatedEvent</tt> that notified us + * {@inheritDoc} + * + * Not used. */ - public void fileTransferCreated(FileTransferCreatedEvent event) - {} + public void messageDeliveryFailed( + AdHocChatRoomMessageDeliveryFailedEvent ev) {} /** - * Called when a new <tt>IncomingFileTransferRequest</tt> has been rejected. - * Nothing to do here, because we are the one who rejects the request. + * {@inheritDoc} * - * @param event the <tt>FileTransferRequestEvent</tt> containing the - * received request which was rejected. + * Not used. */ - public void fileTransferRequestRejected(FileTransferRequestEvent event) - {} + public void messageDeliveryFailed(ChatRoomMessageDeliveryFailedEvent ev) {} /** - * Called when an <tt>IncomingFileTransferRequest</tt> has been canceled - * from the contact who sent it. + * {@inheritDoc} * - * @param event the <tt>FileTransferRequestEvent</tt> 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 <tt>AdHocChatRoomMessageListener.messageReceived</tt> + * method. + * <br> + * @param evt the <tt>AdHocChatRoomMessageReceivedEvent</tt> 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 <tt>ChatRoomMessageListener.messageReceived</tt> method. + * <br> + * Obtains the corresponding <tt>ChatPanel</tt> and process the message + * there. + * @param evt the <tt>ChatRoomMessageReceivedEvent</tt> 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<Map.Entry<Contact, Long>> entries - = this.proactiveTimer.entrySet().iterator(); - while (entries.hasNext()) - { - Map.Entry<Contact, Long> 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 <tt>TypingNotificationEvent</tt> 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 <tt>CallEvent</tt> + * Fired on new messages. + * @param evt the <tt>MessageReceivedEvent</tt> 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<String,String> peerInfo = new HashMap<String, String>(); - 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 class NotificationManager } /** - * Implements CallListener.callEnded. Stops sounds that are playing at - * the moment if there're any. - * @param event the <tt>CallEvent</tt> - */ - 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 <tt>CallChangeListener.callPeerAdded</tt> method. - * @param evt the <tt>CallPeerEvent</tt> 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 <tt>CallChangeListener.callPeerRemoved</tt> method. - * @param evt the <tt>CallPeerEvent</tt> 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 <tt>CallChangeEvent</tt> 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<Boolean>() + { + 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 class NotificationManager //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<Boolean>() + { + 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<Boolean>() + { + 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 @@ public class NotificationManager } /** - * 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 <tt>Recorder</tt> has + * stopped recording the media associated with it. * - * @param evt fired CallPeerEvent - */ - public void peerImageChanged(CallPeerChangeEvent evt) - {} - - /** - * When a <tt>securityOnEvent</tt> is received. - * @param evt the event we received + * @param recorder the <tt>Recorder</tt> 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)); - String messageTitle = null; + // Register incoming call notifications. + notificationService.registerDefaultNotificationForEvent( + INCOMING_CALL, + NotificationAction.ACTION_POPUP_MESSAGE, + null, + null); - 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); - } - } + SoundNotificationAction inCallSoundHandler + = new SoundNotificationAction( + SoundProperties.INCOMING_CALL, 2000, true, true, true); - fireNotification( + 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. + 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); + + // 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)); + + // 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)); + + // Register sound notification for incoming files. + notificationService.registerDefaultNotificationForEvent( + INCOMING_FILE, + NotificationAction.ACTION_POPUP_MESSAGE, + null, + null); + + 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); } /** - * Implements the <tt>ChatRoomMessageListener.messageReceived</tt> method. - * <br> - * Obtains the corresponding <tt>ChatPanel</tt> and process the message - * there. - * @param evt the <tt>ChatRoomMessageReceivedEvent</tt> that notified us - * that a message has been received + * Processes the received security message. + * @param ev the event we received */ - public void messageReceived(ChatRoomMessageReceivedEvent evt) + public void securityMessageRecieved(CallPeerSecurityMessageEvent ev) { try { - ChatRoom sourceChatRoom = evt.getSourceChatRoom(); - ChatRoomMember sourceMember = evt.getSourceChatRoomMember(); - - // Fire notification - boolean fireChatNotification; - - String messageContent = evt.getMessage().getContent(); + String messageTitleKey; - /* - * 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 + 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 <tt>ChatRoomMessageListener.messageDelivered</tt> method. - * <br> - * @param evt the <tt>ChatRoomMessageDeliveredEvent</tt> 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 <tt>ChatRoomMessageListener.messageDeliveryFailed</tt> - * method. - * <br> - * @param evt the <tt>ChatRoomMessageDeliveryFailedEvent</tt> that notified - * us of a delivery failure + * {@inheritDoc} + * + * Not used. */ - public void messageDeliveryFailed(ChatRoomMessageDeliveryFailedEvent evt) - {} + public void securityOff(CallPeerSecurityOffEvent ev) {} /** - * Implements the - * <tt>LocalUserChatRoomPresenceListener.localUserPresenceChanged</tt> - * method. - * @param evt the <tt>LocalUserChatRoomPresenceChangeEvent</tt> that - * notified us + * When a <tt>securityOnEvent</tt> 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 - * <tt>LocalUserAdHocChatRoomPresenceListener.localUserPresenceChanged</tt> - * method + * {@inheritDoc} * - * @param evt the <tt>LocalUserAdHocChatRoomPresenceChangeEvent</tt> 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 <tt>AdHocChatRoomMessageListener.messageReceived</tt> - * method. - * <br> - * @param evt the <tt>AdHocChatRoomMessageReceivedEvent</tt> that notified - * us + * Implements the <tt>ServiceListener</tt> method. Verifies whether the + * passed event concerns a <tt>ProtocolProviderService</tt> and adds the + * corresponding listeners. + * + * @param event The <tt>ServiceEvent</tt> 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 <tt>ChatRoomMessageListener.messageDelivered</tt> method. - * <br> - * @param evt the <tt>ChatRoomMessageDeliveredEvent</tt> that notified us - * that the message was delivered to its destination - */ - public void messageDelivered(AdHocChatRoomMessageDeliveredEvent evt) - {} - - /** - * Implements <tt>AdHocChatRoomMessageListener.messageDeliveryFailed</tt> - * method. - * <br> - * In the conversation area shows an error message, explaining the problem. - * @param evt the <tt>AdHocChatRoomMessageDeliveryFailedEvent</tt> that - * notified us - */ - public void messageDeliveryFailed(AdHocChatRoomMessageDeliveryFailedEvent evt) - {} - - /** - * Call peer has changed. - * @param conferenceEvent - * a <tt>CallPeerConferenceEvent</tt> with ID - * <tt>CallPeerConferenceEvent#CONFERENCE_FOCUS_CHANGED</tt> + * {@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 <tt>Recorder</tt> has - * stopped recording the media associated with it. - * - * @param recorder the <tt>Recorder</tt> 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<Map.Entry<Contact, Long>> entries + = proactiveTimer.entrySet().iterator(); + + while (entries.hasNext()) + { + Map.Entry<Contact, Long> 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 b8983dd..17958d2 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 @@ public class ReconnectPluginActivator 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 8540166..e59e73a 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<String,String> cmdargs); + public void execute( + CommandNotificationAction action, + Map<String,String> cmdargs); } diff --git a/src/net/java/sip/communicator/service/notification/NotificationData.java b/src/net/java/sip/communicator/service/notification/NotificationData.java index d88b6e6..fb88de0 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 @@ import java.util.*; */
public class NotificationData
{
+ /**
+ * The name/key of the <tt>NotificationData</tt> extra which is provided to
+ * {@link CommandNotificationHandler#execute(CommandNotificationAction,
+ * Map)} i.e. a <tt>Map<String,String></tt> which is known by the
+ * (argument) name <tt>cmdargs</tt>.
+ */
+ public static final String COMMAND_NOTIFICATION_HANDLER_CMDARGS_EXTRA
+ = "CommandNotificationHandler.cmdargs";
+
+ /**
+ * The name/key of the <tt>NotificationData</tt> extra which is provided to
+ * {@link PopupMessageNotificationHandler#popupMessage(
+ * PopupMessageNotificationAction, String, String, byte[], Object)} i.e. an
+ * <tt>Object</tt> which is known by the (argument) name <tt>tag</tt>.
+ */
+ public static final String POPUP_MESSAGE_HANDLER_TAG_EXTRA
+ = "PopupMessageNotificationHandler.tag";
+
+ /**
+ * The name/key of the <tt>NotificationData</tt> extra which is provided to
+ * {@link SoundNotificationHandler} i.e. a <tt>Callable<Boolean></tt>
+ * 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<String,String> extra;
+
+ /**
+ * The {@link NotificationHandler}-specific extras provided to this
+ * instance. The keys are among the <tt>XXX_EXTRA</tt> constants defined by
+ * the <tt>NotificationData</tt> class.
+ */
+ private final Map<String, Object> 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
+ * <tt>NotificationHandler</tt>s
*/
- NotificationData(String eventType, String title, String message,
- Map<String,String> extra, byte[] icon, Object tag)
+ NotificationData(
+ String eventType,
+ String title,
+ String message,
+ byte[] icon,
+ Map<String, Object> 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 class NotificationData }
/**
- * Gets the title of the given message.
- *
- * @return the title
+ * Gets the {@link NotificationHandler}-specific extras provided to this
+ * instance.
+ *
+ * @return the <tt>NotificationHandler</tt>-specific extras provided to this
+ * instance. The keys are among the <tt>XXX_EXTRA</tt> constants defined by
+ * the <tt>NotificationData</tt> class
*/
- String getTitle()
+ Map<String, Object> 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 <tt>NotificationHandler</tt>-specific
+ * extra is to be returned. Well known keys are defined by the
+ * <tt>NotificationData</tt> class as the <tt>XXX_EXTRA</tt> constants.
+ * @return the <tt>NotificationHandler</tt>-specific extra provided to this
+ * instance associated with the specified <tt>key</tt>
*/
- 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<String,String> 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 c0b61ea..5de9a31 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 interface NotificationService * <p> * This method does nothing if the given <tt>eventType</tt> is not contained * in the list of registered event types. + * </p> * * @param eventType the type of the event that we'd like to fire a * notification for. @@ -243,7 +244,6 @@ public interface NotificationService * @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 interface NotificationService public NotificationData fireNotification( String eventType, String messageTitle, String message, - byte[] icon, - Object tag); + byte[] icon); /** * Fires all notifications registered for the specified <tt>eventType</tt> @@ -268,19 +267,21 @@ public interface NotificationService * (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 <tt>NotificationData</tt> + * <tt>XXX_EXTRA</tt> constants. * @return An object referencing the notification. It may be used to stop a * still running notification. Can be null if the eventType is * unknown or the notification is not active. */ - public NotificationData fireNotification( String eventType, - String messageTitle, - String message, - Map<String,String> extra, - byte[] icon, - Object tag); + public NotificationData fireNotification( + String eventType, + String messageTitle, + String message, + byte[] icon, + Map<String,Object> extras); /** * Fires all notifications registered for the specified <tt>eventType</tt> diff --git a/src/net/java/sip/communicator/service/notification/NotificationServiceImpl.java b/src/net/java/sip/communicator/service/notification/NotificationServiceImpl.java index 648b8bd..64a7369 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 @@ import org.jitsi.service.configuration.*; 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 <tt>NotificationChangeListener</tt>s. */ - private final Map<String, Notification> notifications - = new HashMap<String, Notification>(); + private final List<NotificationChangeListener> changeListeners + = new Vector<NotificationChangeListener>(); + + private final ConfigurationService configService = + NotificationServiceActivator.getConfigurationService(); /** * A set of all registered event notifications. @@ -60,11 +57,8 @@ class NotificationServiceImpl private final Map<String, NotificationHandler> handlers = new HashMap<String, NotificationHandler>(); - /** - * A list of all registered <tt>NotificationChangeListener</tt>s. - */ - private final List<NotificationChangeListener> changeListeners - = new Vector<NotificationChangeListener>(); + private final Logger logger + = Logger.getLogger(NotificationServiceImpl.class); /** * Queue to cache fired notifications before all handlers are registered. @@ -73,6 +67,12 @@ class NotificationServiceImpl = new LinkedList<NotificationData>(); /** + * A set of all registered event notifications. + */ + private final Map<String, Notification> notifications + = new HashMap<String, Notification>(); + + /** * Creates an instance of <tt>NotificationServiceImpl</tt> by loading all * previously saved notifications. */ @@ -83,198 +83,31 @@ class NotificationServiceImpl } /** - * Creates a new <tt>EventNotification</tt> 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 <tt>NotificationAction</tt> responsible for - * handling the given <tt>actionType</tt> + * @param handler The handler that executes the action. */ - public void registerNotificationForEvent( String eventType, - NotificationAction action) + public void addActionHandler(NotificationHandler handler) { - Notification notification = null; - - if(notifications.containsKey(eventType)) - notification = notifications.get(eventType); - else - { - notification = new Notification(eventType); - notifications.put(eventType, notification); - - this.fireNotificationEventTypeEvent( - EVENT_TYPE_ADDED, eventType); - } - - Object existingAction = notification.addAction(action); + if(handler == null) + throw new IllegalArgumentException("handler cannot be 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 + synchronized(handlers) { - fireNotificationActionTypeEvent( - ACTION_ADDED, - eventType, - action); - } - - // Save the notification through the ConfigurationService. - this.saveNotification(eventType, - action, - true, - false); - } - - /** - * Creates a new <tt>EventNotification</tt> or obtains the corresponding - * existing one and registers a new action in it. - * - * @param eventType the name of the event (as defined by the plugin that's - * registering it) that we are setting an action for. - * @param actionType the type of the action that is to be executed when the - * specified event occurs (could be one of the ACTION_XXX fields). - * @param actionDescriptor a String containing a description of the action - * (a URI to the sound file for audio notifications or a command line for - * exec action types) that should be executed when the action occurs. - * @param defaultMessage the default message to use if no specific message - * has been provided when firing the notification. - */ - public void registerNotificationForEvent( String eventType, - String actionType, - String actionDescriptor, - String defaultMessage) - { - if (logger.isDebugEnabled()) - logger.debug("Registering event " + eventType + "/" + - actionType + "/" + actionDescriptor + "/" + defaultMessage); + handlers.put(handler.getActionType(), handler); + if((handlers.size() == NUM_ACTIONS) && (notificationCache != null)) + { + for(NotificationData event : notificationCache) + fireNotification(event); - 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)); + notificationCache.clear(); + notificationCache = null; + } } } /** - * Removes the <tt>EventNotification</tt> corresponding to the given - * <tt>eventType</tt> from the table of registered event notifications. - * - * @param eventType the name of the event (as defined by the plugin that's - * registering it) to be removed. - */ - public void removeEventNotification(String eventType) - { - notifications.remove(eventType); - - this.fireNotificationEventTypeEvent( - EVENT_TYPE_REMOVED, eventType); - } - - /** - * Removes the given actionType from the list of actions registered for the - * given <tt>eventType</tt>. - * - * @param eventType the name of the event (as defined by the plugin that's - * registering it) for which we'll remove the notification. - * @param actionType the type of the action that is to be executed when the - * specified event occurs (could be one of the ACTION_XXX fields). - */ - public void removeEventNotificationAction( String eventType, - String actionType) - { - Notification notification - = notifications.get(eventType); - - if(notification == null) - return; - - NotificationAction action = notification.getAction(actionType); - - if(action == null) - return; - - notification.removeAction(actionType); - - saveNotification( - eventType, - action, - false, - false); - - fireNotificationActionTypeEvent( - ACTION_REMOVED, - eventType, - action); - } - - /** - * Returns an iterator over a list of all events registered in this - * notification service. Each line in the returned list consists of - * a String, representing the name of the event (as defined by the plugin - * that registered it). - * - * @return an iterator over a list of all events registered in this - * notifications service - */ - public Iterable<String> getRegisteredEvents() - { - return Collections.unmodifiableSet( - notifications.keySet()); - } - - /** - * Returns the notification action corresponding to the given - * <tt>eventType</tt> and <tt>actionType</tt>. - * - * @param eventType the type of the event that we'd like to retrieve. - * @param actionType the type of the action that we'd like to retrieve a - * descriptor for. - * @return the notification action of the action to be executed - * when an event of the specified type has occurred. - */ - public NotificationAction getEventNotificationAction( - String eventType, - String actionType) - { - Notification notification = notifications.get(eventType); - - if(notification == null) - return null; - - return notification.getAction(actionType); - } - - /** * Adds the given <tt>listener</tt> to the list of change listeners. * * @param listener the listener that we'd like to register to listen for @@ -290,80 +123,86 @@ class NotificationServiceImpl } /** - * Removes the given <tt>listener</tt> from the list of change listeners. - * - * @param listener the listener that we'd like to remove + * 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 removeNotificationChangeListener( - NotificationChangeListener listener) + private void checkDefaultAgainstLoadedNotification + (String eventType, NotificationAction defaultAction) { - synchronized (changeListeners) + // checking for new sound action properties + if(defaultAction instanceof SoundNotificationAction) { - changeListeners.remove(listener); - } - } + SoundNotificationAction soundDefaultAction + = (SoundNotificationAction)defaultAction; + SoundNotificationAction soundAction = (SoundNotificationAction) + getEventNotificationAction(eventType, ACTION_SOUND); - /** - * 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"); + boolean isSoundNotificationEnabledPropExist + = getNotificationActionProperty( + eventType, + defaultAction, + "isSoundNotificationEnabled") != null; - synchronized(handlers) - { - handlers.put(handler.getActionType(), handler); - if(handlers.size() == NUM_ACTIONS && notificationCache != null) + if(!isSoundNotificationEnabledPropExist) { - for(NotificationData event : notificationCache) - fireNotification(event); + soundAction.setSoundNotificationEnabled( + soundDefaultAction.isSoundNotificationEnabled()); + } - notificationCache.clear(); - notificationCache = null; + boolean isSoundPlaybackEnabledPropExist + = getNotificationActionProperty( + eventType, + defaultAction, + "isSoundPlaybackEnabled") != null; + + if(!isSoundPlaybackEnabledPropExist) + { + soundAction.setSoundPlaybackEnabled( + soundDefaultAction.isSoundPlaybackEnabled()); } - } - } - /** - * 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"); + boolean isSoundPCSpeakerEnabledPropExist + = getNotificationActionProperty( + eventType, + defaultAction, + "isSoundPCSpeakerEnabled") != null; - synchronized(handlers) - { - handlers.remove(actionType); - } - } + if(!isSoundPCSpeakerEnabledPropExist) + { + soundAction.setSoundPCSpeakerEnabled( + soundDefaultAction.isSoundPCSpeakerEnabled()); + } - /** - * Gets a list of handler for the specified action type. - * - * @param actionType the type for which the list of handlers should be - * retrieved or <tt>null</tt> if all handlers shall be returned. - */ - public Iterable<NotificationHandler> getActionHandlers(String actionType) - { - if (actionType != null) - { - NotificationHandler handler = handlers.get(actionType); - Set<NotificationHandler> ret; + boolean fixDialingLoop = false; - if (handler == null) - ret = Collections.emptySet(); - else - ret = Collections.singleton(handler); - return ret; + // 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); + } } - else - return handlers.values(); } /** @@ -373,71 +212,84 @@ class NotificationServiceImpl private void fireNotification(NotificationData data) { Notification notification = notifications.get(data.getEventType()); - if(notification == null || !notification.isActive()) + + if((notification == null) || !notification.isActive()) return; for(NotificationAction action : notification.getActions().values()) { String actionType = action.getActionType(); - if(!action.isEnabled() || !handlers.containsKey(actionType)) + + if(!action.isEnabled()) continue; NotificationHandler handler = handlers.get(actionType); + + if (handler == null) + continue; + 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<String, String> cmdargs + = (Map<String, String>) + 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 + * <tt>eventType</tt> and the event notification is currently activated, we + * go through the list of registered actions and execute them. + * + * @param eventType the type of the event that we'd like to fire a + * notification for. + * + * @return An object referencing the notification. It may be used to stop a + * still running notification. Can be null if the eventType is + * unknown or the notification is not active. */ - public void stopNotification(NotificationData data) + public NotificationData fireNotification(String eventType) { - Iterable<NotificationHandler> soundHandlers - = getActionHandlers(NotificationAction.ACTION_SOUND); - - // There could be no sound action handler for this event type - if (soundHandlers != null) - { - for (NotificationHandler handler : soundHandlers) - { - if (handler instanceof SoundNotificationHandler) - ((SoundNotificationHandler) handler).stop(data); - } - } + return fireNotification(eventType, null, null, null); } /** @@ -461,15 +313,9 @@ class NotificationServiceImpl 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 @@ class NotificationServiceImpl * @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 <tt>NotificationData</tt> + * <tt>XXX_EXTRA</tt> constants. * * @return An object referencing the notification. It may be used to stop a * still running notification. Can be null if the eventType is * unknown or the notification is not active. */ public NotificationData fireNotification( - String eventType, - String title, - String message, - Map<String,String> extra, - byte[] icon, - Object tag) + String eventType, + String title, + String message, + byte[] icon, + Map<String,Object> 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,35 +363,137 @@ class NotificationServiceImpl } /** - * If there is a registered event notification of the given - * <tt>eventType</tt> and the event notification is currently activated, we - * go through the list of registered actions and execute them. + * Notifies all registered <tt>NotificationChangeListener</tt>s that a + * <tt>NotificationActionTypeEvent</tt> has occurred. * - * @param eventType the type of the event that we'd like to fire a - * notification for. + * @param eventType the type of the event, which is one of ACTION_XXX + * constants declared in the <tt>NotificationActionTypeEvent</tt> class. + * @param sourceEventType the <tt>eventType</tt>, which is the parent of the + * action + * @param action the notification action + */ + private void fireNotificationActionTypeEvent( + String eventType, + String sourceEventType, + NotificationAction action) + { + NotificationActionTypeEvent event + = new NotificationActionTypeEvent( this, + eventType, + sourceEventType, + action); + + + for(NotificationChangeListener listener : changeListeners) + { + if (eventType.equals(ACTION_ADDED)) + { + listener.actionAdded(event); + } + else if (eventType.equals(ACTION_REMOVED)) + { + listener.actionRemoved(event); + } + else if (eventType.equals(ACTION_CHANGED)) + { + listener.actionChanged(event); + } + } + } + + /** + * Notifies all registered <tt>NotificationChangeListener</tt>s that a + * <tt>NotificationEventTypeEvent</tt> has occurred. * - * @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 EVENT_TYPE_XXX + * constants declared in the <tt>NotificationEventTypeEvent</tt> class. + * @param sourceEventType the <tt>eventType</tt>, for which this event is + * about */ - public NotificationData fireNotification(String eventType) + private void fireNotificationEventTypeEvent(String eventType, + String sourceEventType) { - return this.fireNotification(eventType, null, null, null, null, null); + 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); + } + } } /** - * Saves the event notification given by these parameters through the - * <tt>ConfigurationService</tt>. + * Gets a list of handler for the specified action type. * - * @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 actionType the type for which the list of handlers should be + * retrieved or <tt>null</tt> if all handlers shall be returned. */ - private void saveNotification( String eventType, - NotificationAction action, - boolean isActive, - boolean isDefault) + public Iterable<NotificationHandler> getActionHandlers(String actionType) + { + if (actionType != null) + { + NotificationHandler handler = handlers.get(actionType); + Set<NotificationHandler> ret; + + if (handler == null) + ret = Collections.emptySet(); + else + ret = Collections.singleton(handler); + return ret; + } + else + return handlers.values(); + } + + /** + * Returns the notification action corresponding to the given + * <tt>eventType</tt> and <tt>actionType</tt>. + * + * @param eventType the type of the event that we'd like to retrieve. + * @param actionType the type of the action that we'd like to retrieve a + * descriptor for. + * @return the notification action of the action to be executed + * when an event of the specified type has occurred. + */ + public NotificationAction getEventNotificationAction( + String eventType, + String actionType) + { + Notification notification = notifications.get(eventType); + + return + (notification == null) ? null : notification.getAction(actionType); + } + + /** + * Getting a notification property directly from configuration service. + * Used to check do we have an updated version of already saved/edited + * notification configurations. Detects old configurations. + * + * @param eventType the event type + * @param action the action which property to check. + * @param property the property name without the action prefix. + * @return the property value or null if missing. + * @throws IllegalArgumentException when the event ot action is not + * found. + */ + private String getNotificationActionProperty( + String eventType, + NotificationAction action, + String property) + throws IllegalArgumentException { String eventTypeNodeName = null; String actionTypeNodeName = null; @@ -558,24 +508,11 @@ class NotificationServiceImpl eventTypeNodeName = eventTypeRootPropName; } - // If we didn't find the given event type in the configuration we save - // it here. + // If we didn't find the given event type in the configuration + // there is not need to further check if(eventTypeNodeName == null) { - eventTypeNodeName = NOTIFICATIONS_PREFIX - + ".eventType" - + Long.toString(System.currentTimeMillis()); - - configService.setProperty(eventTypeNodeName, eventType); - } - - // if we set active/inactive for the whole event notification - if(action == null) - { - configService.setProperty( - eventTypeNodeName + ".active", - Boolean.toString(isActive)); - return; + throw new IllegalArgumentException("Missing event type node"); } // Go through contained actions. @@ -591,81 +528,102 @@ class NotificationServiceImpl actionTypeNodeName = actionTypeRootPropName; } - Map<String, Object> configProperties = new HashMap<String, Object>(); - - // If we didn't find the given actionType in the configuration we save - // it here. + // If 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<String> getRegisteredEvents() + { + return Collections.unmodifiableSet( + notifications.keySet()); + } - configProperties.put( - actionTypeNodeName + ".soundFileDescriptor", - soundAction.getDescriptor()); + /** + * Finds the <tt>EventNotification</tt> corresponding to the given + * <tt>eventType</tt> and returns its isActive status. + * + * @param eventType the name of the event (as defined by the plugin that's + * registered it) that we are checking. + * @return <code>true</code> if actions for the specified <tt>eventType</tt> + * are activated, <code>false</code> - otherwise. If the given + * <tt>eventType</tt> is not contained in the list of registered event + * types - returns <code>false</code>. + */ + public boolean isActive(String eventType) + { + Notification eventNotification + = notifications.get(eventType); - 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<String> 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<String> 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,172 +724,6 @@ class NotificationServiceImpl } } - 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 <tt>EventNotification</tt> corresponding to the given - * <tt>eventType</tt> and marks it as activated/deactivated. - * - * @param eventType the name of the event, which actions should be activated - * /deactivated. - * @param isActive indicates whether to activate or deactivate the actions - * related to the specified <tt>eventType</tt>. - */ - public void setActive(String eventType, boolean isActive) - { - Notification eventNotification - = notifications.get(eventType); - - if(eventNotification == null) - return; - - eventNotification.setActive(isActive); - saveNotification(eventType, null, isActive, false); - } - - /** - * Finds the <tt>EventNotification</tt> corresponding to the given - * <tt>eventType</tt> and returns its isActive status. - * - * @param eventType the name of the event (as defined by the plugin that's - * registered it) that we are checking. - * @return <code>true</code> if actions for the specified <tt>eventType</tt> - * are activated, <code>false</code> - otherwise. If the given - * <tt>eventType</tt> is not contained in the list of registered event - * types - returns <code>false</code>. - */ - public boolean isActive(String eventType) - { - Notification eventNotification - = notifications.get(eventType); - - if(eventNotification == null) - return false; - - return eventNotification.isActive(); - } - - /** - * Notifies all registered <tt>NotificationChangeListener</tt>s that a - * <tt>NotificationEventTypeEvent</tt> has occurred. - * - * @param eventType the type of the event, which is one of EVENT_TYPE_XXX - * constants declared in the <tt>NotificationEventTypeEvent</tt> class. - * @param sourceEventType the <tt>eventType</tt>, for which this event is - * about - */ - private void fireNotificationEventTypeEvent(String eventType, - String sourceEventType) - { - if (logger.isDebugEnabled()) - logger.debug("Dispatching NotificationEventType Change. Listeners=" - + changeListeners.size() - + " evt=" + eventType); - - NotificationEventTypeEvent event - = new NotificationEventTypeEvent(this, eventType, sourceEventType); - - for (NotificationChangeListener listener : changeListeners) - { - if (eventType.equals(EVENT_TYPE_ADDED)) - { - listener.eventTypeAdded(event); - } - else if (eventType.equals(EVENT_TYPE_REMOVED)) - { - listener.eventTypeRemoved(event); - } - } - } - - /** - * Notifies all registered <tt>NotificationChangeListener</tt>s that a - * <tt>NotificationActionTypeEvent</tt> has occurred. - * - * @param eventType the type of the event, which is one of ACTION_XXX - * constants declared in the <tt>NotificationActionTypeEvent</tt> class. - * @param sourceEventType the <tt>eventType</tt>, which is the parent of the - * action - * @param action the notification action - */ - private void fireNotificationActionTypeEvent( - String eventType, - String sourceEventType, - NotificationAction action) - { - NotificationActionTypeEvent event - = new NotificationActionTypeEvent( this, - eventType, - sourceEventType, - action); - - - for(NotificationChangeListener listener : changeListeners) - { - if (eventType.equals(ACTION_ADDED)) - { - listener.actionAdded(event); - } - else if (eventType.equals(ACTION_REMOVED)) - { - listener.actionRemoved(event); - } - else if (eventType.equals(ACTION_CHANGED)) - { - listener.actionChanged(event); - } - } - } - - private boolean isDefault(String eventType, String actionType) - { - List<String> eventTypes = configService - .getPropertyNamesByPrefix(NOTIFICATIONS_PREFIX, true); - - for (String eventTypeRootPropName : eventTypes) - { - String eType - = configService.getString(eventTypeRootPropName); - - if(!eType.equals(eventType)) - continue; - - List<String> actions = configService - .getPropertyNamesByPrefix( - eventTypeRootPropName + ".actions", true); - - for (String actionPropName : actions) - { - String aType - = configService.getString(actionPropName); - - if(!aType.equals(actionType)) - continue; - - Object isDefaultdObj = - configService.getProperty(actionPropName + ".default"); - - // if setting is missing we accept it is true - // this way we override old saved settings - if(isDefaultdObj == null) - return true; - else - return Boolean.parseBoolean((String)isDefaultdObj); - } - } - return true; - } - /** * Creates a new default <tt>EventNotification</tt> or obtains the * corresponding existing one and registers a new action in it. @@ -1114,105 +906,230 @@ class NotificationServiceImpl } /** - * 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 <tt>EventNotification</tt> or obtains the corresponding + * existing one and registers a new action in it. + * + * @param eventType the name of the event (as defined by the plugin that's + * registering it) that we are setting an action for. + * @param action the <tt>NotificationAction</tt> responsible for + * handling the given <tt>actionType</tt> */ - 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( + /** + * Creates a new <tt>EventNotification</tt> or obtains the corresponding + * existing one and registers a new action in it. + * + * @param eventType the name of the event (as defined by the plugin that's + * registering it) that we are setting an action for. + * @param actionType the type of the action that is to be executed when the + * specified event occurs (could be one of the ACTION_XXX fields). + * @param actionDescriptor a String containing a description of the action + * (a URI to the sound file for audio notifications or a command line for + * exec action types) that should be executed when the action occurs. + * @param defaultMessage the default message to use if no specific message + * has been provided when firing the notification. + */ + public void registerNotificationForEvent( String eventType, + String actionType, + String actionDescriptor, + String defaultMessage) + { + if (logger.isDebugEnabled()) + logger.debug("Registering event " + eventType + "/" + + actionType + "/" + actionDescriptor + "/" + defaultMessage); + + if (actionType.equals(ACTION_SOUND)) + { + Notification notification = defaultNotifications.get(eventType); + SoundNotificationAction action = + (SoundNotificationAction) notification.getAction(ACTION_SOUND); + registerNotificationForEvent ( eventType, - defaultAction, - "isSoundPCSpeakerEnabled") != null; + 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)); + } + } - if(!isSoundPCSpeakerEnabledPropExist) - { - soundAction.setSoundPCSpeakerEnabled( - soundDefaultAction.isSoundPCSpeakerEnabled()); - } + /** + * 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"); - boolean fixDialingLoop = false; + synchronized(handlers) + { + handlers.remove(actionType); + } + } - // 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; - } + /** + * Removes the <tt>EventNotification</tt> corresponding to the given + * <tt>eventType</tt> from the table of registered event notifications. + * + * @param eventType the name of the event (as defined by the plugin that's + * registering it) to be removed. + */ + public void removeEventNotification(String eventType) + { + notifications.remove(eventType); + + this.fireNotificationEventTypeEvent( + EVENT_TYPE_REMOVED, eventType); + } - 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 <tt>eventType</tt>. + * + * @param eventType the name of the event (as defined by the plugin that's + * registering it) for which we'll remove the notification. + * @param actionType the type of the action that is to be executed when the + * specified event occurs (could be one of the ACTION_XXX fields). + */ + public void removeEventNotificationAction( String eventType, + String actionType) + { + Notification notification + = notifications.get(eventType); + + if(notification == null) + return; + + NotificationAction action = notification.getAction(actionType); + + if(action == null) + return; + + notification.removeAction(actionType); + + saveNotification( + eventType, + action, + false, + false); + + fireNotificationActionTypeEvent( + ACTION_REMOVED, + eventType, + action); + } + + /** + * Removes the given <tt>listener</tt> from the list of change listeners. + * + * @param listener the listener that we'd like to remove + */ + public void removeNotificationChangeListener( + NotificationChangeListener listener) + { + synchronized (changeListeners) + { + changeListeners.remove(listener); } } /** - * 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<String>(notifications.keySet())) + { + Notification notification = notifications.get(eventType); + + for (String actionType + : new Vector<String>(notification.getActions().keySet())) + removeEventNotificationAction(eventType, actionType); + + removeEventNotification(eventType); + } + + for (Map.Entry<String, Notification> entry + : defaultNotifications.entrySet()) + { + String eventType = entry.getKey(); + Notification notification = entry.getValue(); + + for (NotificationAction action : notification.getActions().values()) + registerNotificationForEvent(eventType, action); + } + } + + /** + * Saves the event notification given by these parameters through the + * <tt>ConfigurationService</tt>. + * + * @param eventType the name of the event + * @param action the notification action to change + * @param isActive is the event active + * @param isDefault is it a default one + */ + private void saveNotification( String eventType, + NotificationAction action, + boolean isActive, + boolean isDefault) { String eventTypeNodeName = null; String actionTypeNodeName = null; @@ -1227,11 +1144,24 @@ class NotificationServiceImpl 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 @@ class NotificationServiceImpl Map<String, Object> configProperties = new HashMap<String, Object>(); - // 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 <tt>EventNotification</tt> corresponding to the given + * <tt>eventType</tt> and marks it as activated/deactivated. + * + * @param eventType the name of the event, which actions should be activated + * /deactivated. + * @param isActive indicates whether to activate or deactivate the actions + * related to the specified <tt>eventType</tt>. */ - public void restoreDefaults() + public void setActive(String eventType, boolean isActive) { - for (String eventType : new Vector<String>(notifications.keySet())) - { - Notification notification = notifications.get(eventType); + Notification eventNotification + = notifications.get(eventType); - for (String actionType - : new Vector<String>(notification.getActions().keySet())) - removeEventNotificationAction(eventType, actionType); + if(eventNotification == null) + return; - removeEventNotification(eventType); - } + eventNotification.setActive(isActive); + saveNotification(eventType, null, isActive, false); + } - for (Map.Entry<String, Notification> 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<NotificationHandler> 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 e8b5716..d9cdfd0 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 <tt>SystrayPopupMessageEvent</tt>s 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 0a5f4fb..cae92fc 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 @@ public class ConfigurableDnssecResolver || 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 babec92..01b6f3b 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 junit.framework.*; 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 @@ import org.osgi.framework.*; public class TestPopupMessageHandler extends TestCase { - /** Logger for this class */ - private static final Logger logger - = Logger.getLogger(TestPopupMessageHandler.class); - /** * the <tt>SystrayService</tt> reference we will get from bundle * context to register ours handlers @@ -121,7 +115,6 @@ public class TestPopupMessageHandler { serviceReference = bc.getServiceReference( NotificationService.class.getName()); - notificationService = (NotificationService) bc.getService(serviceReference); @@ -129,7 +122,6 @@ public class TestPopupMessageHandler NotificationAction.ACTION_POPUP_MESSAGE, messageStart, messageStart, - null, null); } |