/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.java.sip.communicator.plugin.chatalerter;
import javax.swing.*;
import net.java.sip.communicator.service.gui.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.util.*;
import net.java.sip.communicator.util.Logger;
import org.jdesktop.jdic.misc.*;
import org.jitsi.service.configuration.*;
import org.jitsi.util.*;
import org.osgi.framework.*;
import java.beans.*;
import java.util.*;
/**
* Chat Alerter plugin.
*
* Sends alerts to the user when new message arrives and the application is not
* in the foreground. On Mac OS X this will bounce the dock icon until the user
* selects the chat windows. On Windows, Gnome and KDE this will flash the
* taskbar button/icon until the user selects the chat window.
*
* @author Damian Minkov
*/
public class ChatAlerterActivator
implements BundleActivator,
ServiceListener,
MessageListener,
ChatRoomMessageListener,
AdHocChatRoomMessageListener,
LocalUserChatRoomPresenceListener,
LocalUserAdHocChatRoomPresenceListener,
PropertyChangeListener,
CallListener
{
/**
* The logger for this class.
*/
private static Logger logger = Logger.getLogger(ChatAlerterActivator.class);
/**
* The BundleContext that we got from the OSGI bus.
*/
private BundleContext bundleContext = null;
/**
* UIService reference.
*/
private UIService uiService;
/**
* Whether we are started.
*/
private boolean started = false;
/**
* Starts this bundle.
* @param bc bundle context.
* @throws Exception
*/
public void start(BundleContext bc) throws Exception
{
this.bundleContext = bc;
ServiceUtils.getService(bundleContext, ConfigurationService.class)
.addPropertyChangeListener(
ConfigurationUtils.ALERTER_ENABLED_PROP, this);
try
{
if(!ConfigurationUtils.isAlerterEnabled())
{
return;
}
// try to load native libs, if it fails don't do anything
if(!OSUtils.IS_MAC)
Alerter.newInstance();
}
catch (Exception exception)
{
if (logger.isInfoEnabled())
logger.info("The Alerter not supported or problem loading it!",
exception);
return;
}
startInternal(bc);
}
/**
* Starts the impl and adds necessary listeners.
* @param bc the current bundle context.
*/
private void startInternal(BundleContext bc)
{
// start listening for newly register or removed protocol providers
bc.addServiceListener(this);
ServiceReference[] protocolProviderRefs;
try
{
protocolProviderRefs = bc.getServiceReferences(
ProtocolProviderService.class.getName(),
null);
}
catch (InvalidSyntaxException ex)
{
// this shouldn't happen since we're providing no parameter string
// but let's log just in case.
logger.error(
"Error while retrieving service refs", ex);
return;
}
// in case we found any
if (protocolProviderRefs != null)
{
if (logger.isDebugEnabled())
logger.debug("Found "
+ protocolProviderRefs.length
+ " already installed providers.");
for (ServiceReference protocolProviderRef : protocolProviderRefs)
{
ProtocolProviderService provider
= (ProtocolProviderService)
bc.getService(protocolProviderRef);
this.handleProviderAdded(provider);
}
}
this.started = true;
}
/**
* Stops bundle.
* @param bc context.
* @throws Exception
*/
public void stop(BundleContext bc) throws Exception
{
stopInternal(bc);
ServiceUtils.getService(bundleContext, ConfigurationService.class)
.removePropertyChangeListener(
ConfigurationUtils.ALERTER_ENABLED_PROP, this);
}
/**
* Stops the impl and removes necessary listeners.
* @param bc the current bundle context.
*/
private void stopInternal(BundleContext bc)
{
// start listening for newly register or removed protocol providers
bc.removeServiceListener(this);
ServiceReference[] protocolProviderRefs;
try
{
protocolProviderRefs = bc.getServiceReferences(
ProtocolProviderService.class.getName(),
null);
}
catch (InvalidSyntaxException ex)
{
// this shouldn't happen since we're providing no parameter string
// but let's log just in case.
logger.error(
"Error while retrieving service refs", ex);
return;
}
// in case we found any
if (protocolProviderRefs != null)
{
for (ServiceReference protocolProviderRef : protocolProviderRefs)
{
ProtocolProviderService provider
= (ProtocolProviderService)
bc.getService(protocolProviderRef);
this.handleProviderRemoved(provider);
}
}
this.started = false;
}
/**
* Used to attach the Alerter plugin to existing or
* just registered protocol provider. Checks if the provider has implementation
* of OperationSetBasicInstantMessaging
*
* @param provider ProtocolProviderService
*/
private void handleProviderAdded(ProtocolProviderService provider)
{
if (logger.isDebugEnabled())
logger.debug("Adding protocol provider " + provider.getProtocolName());
// check whether the provider has a basic im operation set
OperationSetBasicInstantMessaging opSetIm
= provider
.getOperationSet(OperationSetBasicInstantMessaging.class);
if (opSetIm != null)
{
opSetIm.addMessageListener(this);
}
else
{
if (logger.isTraceEnabled())
logger.trace("Service did not have a im op. set.");
}
// check whether the provider has a sms operation set
OperationSetSmsMessaging opSetSms
= provider.getOperationSet(OperationSetSmsMessaging.class);
if (opSetSms != null)
{
opSetSms.addMessageListener(this);
}
else
{
if (logger.isTraceEnabled())
logger.trace("Service did not have a sms op. set.");
}
OperationSetMultiUserChat opSetMultiUChat
= provider.getOperationSet(OperationSetMultiUserChat.class);
if (opSetMultiUChat != null)
{
for (ChatRoom room : opSetMultiUChat.getCurrentlyJoinedChatRooms())
room.addMessageListener(this);
opSetMultiUChat.addPresenceListener(this);
}
else
{
if (logger.isTraceEnabled())
logger.trace("Service did not have a multi im op. set.");
}
OperationSetBasicTelephony<?> basicTelephonyOpSet
= provider.getOperationSet(OperationSetBasicTelephony.class);
if (basicTelephonyOpSet != null)
{
basicTelephonyOpSet.addCallListener(this);
}
}
/**
* Removes the specified provider from the list of currently known providers
* and ignores all the messages exchanged by it
*
* @param provider the ProtocolProviderService that has been unregistered.
*/
private void handleProviderRemoved(ProtocolProviderService provider)
{
OperationSetBasicInstantMessaging opSetIm
= provider.getOperationSet(OperationSetBasicInstantMessaging.class);
if (opSetIm != null)
{
opSetIm.removeMessageListener(this);
}
OperationSetSmsMessaging opSetSms
= provider.getOperationSet(OperationSetSmsMessaging.class);
if (opSetSms != null)
{
opSetSms.removeMessageListener(this);
}
OperationSetMultiUserChat opSetMultiUChat
= provider.getOperationSet(OperationSetMultiUserChat.class);
if (opSetMultiUChat != null)
{
for (ChatRoom room : opSetMultiUChat.getCurrentlyJoinedChatRooms())
room.removeMessageListener(this);
}
OperationSetBasicTelephony<?> basicTelephonyOpSet
= provider.getOperationSet(OperationSetBasicTelephony.class);
if (basicTelephonyOpSet != null)
{
basicTelephonyOpSet.removeCallListener(this);
}
}
/**
* Called to notify interested parties that a change in our presence in
* a chat room has occurred. Changes may include us being kicked, join,
* left.
* @param ev the <tt>LocalUserChatRoomPresenceChangeEvent</tt> instance
* containing the chat room and the type, and reason of the change
*/
public void localUserPresenceChanged(LocalUserChatRoomPresenceChangeEvent ev)
{
ChatRoom chatRoom = ev.getChatRoom();
if(LocalUserChatRoomPresenceChangeEvent.LOCAL_USER_JOINED.equals(
ev.getEventType()))
{
if (!chatRoom.isSystem())
chatRoom.addMessageListener(this);
}
else
{
chatRoom.removeMessageListener(this);
}
}
public void messageReceived(MessageReceivedEvent evt)
{
alertChatWindow();
}
public void messageDelivered(MessageDeliveredEvent evt)
{
// do nothing
}
public void messageDeliveryFailed(MessageDeliveryFailedEvent evt)
{
// do nothing
}
public void messageReceived(ChatRoomMessageReceivedEvent evt)
{
alertChatWindow();
}
public void messageDelivered(ChatRoomMessageDeliveredEvent evt)
{
// do nothing
}
public void messageDeliveryFailed(ChatRoomMessageDeliveryFailedEvent evt)
{
// do nothing
}
/**
* Alerts that a message has been received in
* <code>ExportedWindow.CHAT_WINDOW</code> by using a platform-dependent
* visual clue such as flashing it in the task bar on Windows and Linux.
*/
private void alertChatWindow()
{
alertWindow(ExportedWindow.CHAT_WINDOW);
}
/**
* Alerts the <code>windowID</code> by using a platform-dependent
* visual clue such as flashing it in the task bar on Windows and Linux,
* or the bouncing the dock icon under macosx.
*/
private void alertWindow(WindowID windowID)
{
try
{
ExportedWindow win = getUIService().getExportedWindow(windowID);
if (win == null)
return;
Object winSource = win.getSource();
if (!(winSource instanceof JFrame))
return;
JFrame fr = (JFrame) winSource;
if(OSUtils.IS_MAC)
com.apple.eawt.Application.getApplication()
.requestUserAttention(true);
else
Alerter.newInstance().alert(fr);
}
catch (Throwable ex)
{
logger.error("Cannot alert chat window!", ex);
}
}
/**
* When new protocol provider is registered we check
* does it supports needed Op. Sets and if so add a listener to it
*
* @param serviceEvent ServiceEvent
*/
public void serviceChanged(ServiceEvent serviceEvent)
{
Object sService
= bundleContext.getService(serviceEvent.getServiceReference());
if (logger.isTraceEnabled())
logger.trace("Received a service event for: " +
sService.getClass().getName());
// we don't care if the source service is not a protocol provider
if (!(sService instanceof ProtocolProviderService))
return;
if (logger.isDebugEnabled())
logger.debug("Service is a protocol provider.");
switch (serviceEvent.getType())
{
case ServiceEvent.REGISTERED:
this.handleProviderAdded((ProtocolProviderService)sService);
break;
case ServiceEvent.UNREGISTERING:
this.handleProviderRemoved( (ProtocolProviderService) sService);
break;
}
}
public void messageDelivered(AdHocChatRoomMessageDeliveredEvent evt)
{
// do nothing
}
public void messageDeliveryFailed(
AdHocChatRoomMessageDeliveryFailedEvent evt)
{
// do nothing
}
public void messageReceived(AdHocChatRoomMessageReceivedEvent evt)
{
alertChatWindow();
}
/**
* Called to notify interested parties that a change in our presence in
* an ad-hoc chat room has occurred. Changes may include us being join,
* left.
* @param ev the <tt>LocalUserAdHocChatRoomPresenceChangeEvent</tt>
* instance containing the ad-hoc chat room and the type, and reason of the
* change
*/
public void localUserAdHocPresenceChanged(
LocalUserAdHocChatRoomPresenceChangeEvent ev)
{
AdHocChatRoom adHocChatRoom = ev.getAdHocChatRoom();
if(LocalUserAdHocChatRoomPresenceChangeEvent.LOCAL_USER_JOINED.equals(
ev.getEventType()))
{
adHocChatRoom.addMessageListener(this);
}
else
{
ev.getAdHocChatRoom().removeMessageListener(this);
}
}
/**
* Returns the <tt>UIService</tt> obtained from the bundle
* context.
* @return the <tt>UIService</tt> obtained from the bundle
* context
*/
public UIService getUIService()
{
if(uiService == null)
{
ServiceReference serviceRef = bundleContext
.getServiceReference(UIService.class.getName());
if (serviceRef != null)
uiService = (UIService) bundleContext.getService(serviceRef);
}
return uiService;
}
/**
* Waits for enable/disable property change.
* @param evt the event of change
*/
public void propertyChange(PropertyChangeEvent evt)
{
if(!evt.getPropertyName()
.equals(ConfigurationUtils.ALERTER_ENABLED_PROP))
return;
try
{
if(ConfigurationUtils.isAlerterEnabled() && !started)
{
startInternal(bundleContext);
}
else if(!ConfigurationUtils.isAlerterEnabled() && started)
{
stopInternal(bundleContext);
}
}
catch(Throwable t)
{
logger.error("Error starting/stopping on configuration change");
}
}
/**
* This method is called by a protocol provider whenever an incoming call is
* received.
*
* @param event a CallEvent instance describing the new incoming call
*/
public void incomingCallReceived(CallEvent event)
{
Call call = event.getSourceCall();
/*
* INCOMING_CALL should be dispatched for a Call
* only while there is a CallPeer in the
* INCOMING_CALL state.
*/
Iterator<? extends CallPeer> peerIter = call.getCallPeers();
boolean alert = false;
while (peerIter.hasNext())
{
CallPeer peer = peerIter.next();
if (CallPeerState.INCOMING_CALL.equals(peer.getState()))
{
alert = true;
break;
}
}
if(alert)
alertWindow(ExportedWindow.MAIN_WINDOW);
}
/**
* Not used.
* @param event a CalldEvent instance describing the new outgoing call.
*/
public void outgoingCallCreated(CallEvent event)
{}
/**
* Not used
* @param event the <tt>CallEvent</tt> containing the source call.
*/
public void callEnded(CallEvent event)
{}
}