/** * The source contact service. The will show most recent messages. * * @author Damian Minkov */ public class MessageSourceService extends MetaContactListAdapter implements ContactSourceService, ContactPresenceStatusListener, ContactCapabilitiesListener, ProviderPresenceStatusListener, SubscriptionListener, LocalUserChatRoomPresenceListener, MessageListener, ChatRoomMessageListener, AdHocChatRoomMessageListener { /** The logger for this class. */ private static Logger logger = Logger.getLogger(MessageSourceService.class); /** The display name of this contact source. */ private final String MESSAGE_HISTORY_NAME; /** The type of the source service, the place to be shown in the ui. */ private int sourceServiceType = CONTACT_LIST_TYPE; /** * Whether to show recent messages in history or in contactlist. By default we show it in * contactlist. */ private static final String IN_HISTORY_PROPERTY = "net.java.sip.communicator.impl.msghistory.contactsrc.IN_HISTORY"; /** Property to control number of recent messages. */ private static final String NUMBER_OF_RECENT_MSGS_PROP = "net.java.sip.communicator.impl.msghistory.contactsrc.MSG_NUMBER"; /** Property to control version of recent messages. */ private static final String VER_OF_RECENT_MSGS_PROP = "net.java.sip.communicator.impl.msghistory.contactsrc.MSG_VER"; /** Property to control messages type. Can query for message sub type. */ private static final String IS_MESSAGE_SUBTYPE_SMS_PROP = "net.java.sip.communicator.impl.msghistory.contactsrc.IS_SMS_ENABLED"; /** * The number of recent messages to store in the history, but will retrieve just * <tt>numberOfMessages</tt> */ private static final int NUMBER_OF_MSGS_IN_HISTORY = 100; /** Number of messages to show. */ private int numberOfMessages = 10; /** The structure to save recent messages list. */ private static final String[] STRUCTURE_NAMES = new String[] {"provider", "contact", "timestamp", "ver"}; /** The current version of recent messages. When changed the recent messages are recreated. */ private static String RECENT_MSGS_VER = "2"; /** The structure. */ private static final HistoryRecordStructure recordStructure = new HistoryRecordStructure(STRUCTURE_NAMES); /** Recent messages history ID. */ private static final HistoryID historyID = HistoryID.createFromRawID(new String[] {"recent_messages"}); /** The cache for recent messages. */ private History history = null; /** List of recent messages. */ private final List<ComparableEvtObj> recentMessages = new LinkedList<ComparableEvtObj>(); /** Date of the oldest shown message. */ private Date oldestRecentMessage = null; /** The last query created. */ private MessageSourceContactQuery recentQuery = null; /** The message subtype if any. */ private boolean isSMSEnabled = false; /** Message history service that has created us. */ private MessageHistoryServiceImpl messageHistoryService; /** Constructs MessageSourceService. */ MessageSourceService(MessageHistoryServiceImpl messageHistoryService) { this.messageHistoryService = messageHistoryService; ConfigurationService conf = MessageHistoryActivator.getConfigurationService(); if (conf.getBoolean(IN_HISTORY_PROPERTY, false)) { sourceServiceType = HISTORY_TYPE; } MESSAGE_HISTORY_NAME = MessageHistoryActivator.getResources().getI18NString("service.gui.RECENT_MESSAGES"); numberOfMessages = conf.getInt(NUMBER_OF_RECENT_MSGS_PROP, numberOfMessages); isSMSEnabled = conf.getBoolean(IS_MESSAGE_SUBTYPE_SMS_PROP, isSMSEnabled); RECENT_MSGS_VER = conf.getString(VER_OF_RECENT_MSGS_PROP, RECENT_MSGS_VER); MessageSourceContactPresenceStatus.MSG_SRC_CONTACT_ONLINE.setStatusIcon( MessageHistoryActivator.getResources() .getImageInBytes("service.gui.icons.SMS_STATUS_ICON")); } /** * Returns the display name of this contact source. * * @return the display name of this contact source */ @Override public String getDisplayName() { return MESSAGE_HISTORY_NAME; } /** * Returns default type to indicate that this contact source can be queried by default filters. * * @return the type of this contact source */ @Override public int getType() { return sourceServiceType; } /** * Returns the index of the contact source in the result list. * * @return the index of the contact source in the result list */ @Override public int getIndex() { return 0; } /** * Creates query for the given <tt>searchString</tt>. * * @param queryString the string to search for * @return the created query */ @Override public ContactQuery createContactQuery(String queryString) { recentQuery = (MessageSourceContactQuery) createContactQuery(queryString, numberOfMessages); return recentQuery; } /** * Updates the contact sources in the recent query if any. Done here in order to sync with * recentMessages instance, and to check for already existing instances of contact sources. * Normally called from the query. */ public void updateRecentMessages() { if (recentQuery == null) return; synchronized (recentMessages) { List<SourceContact> currentContactsInQuery = recentQuery.getQueryResults(); for (ComparableEvtObj evtObj : recentMessages) { // the contains will use the correct equals method of // the object evtObj if (!currentContactsInQuery.contains(evtObj)) { MessageSourceContact newSourceContact = new MessageSourceContact(evtObj.getEventObject(), MessageSourceService.this); newSourceContact.initDetails(evtObj.getEventObject()); recentQuery.addQueryResult(newSourceContact); } } } } /** * Searches for entries in cached recent messages in history. * * @param provider the provider which contact messages we will search * @param isStatusChanged is the search because of status changed * @return entries in cached recent messages in history. */ private List<ComparableEvtObj> getCachedRecentMessages( ProtocolProviderService provider, boolean isStatusChanged) { String providerID = provider.getAccountID().getAccountUniqueID(); List<String> recentMessagesContactIDs = getRecentContactIDs( providerID, recentMessages.size() < numberOfMessages ? null : oldestRecentMessage); List<ComparableEvtObj> cachedRecentMessages = new ArrayList<ComparableEvtObj>(); for (String contactID : recentMessagesContactIDs) { Collection<EventObject> res = messageHistoryService.findRecentMessagesPerContact( numberOfMessages, providerID, contactID, isSMSEnabled); processEventObjects(res, cachedRecentMessages, isStatusChanged); } return cachedRecentMessages; } /** * Process list of event objects. Checks whether message source contact already exist for this * event object, if yes just update it with the new values (not sure whether we should do this, as * it may bring old messages) and if status of provider is changed, init its details, updates its * capabilities. It still adds the found messages source contact to the list of the new contacts, * as later we will detect this and fire update event. If nothing found a new contact is created. * * @param res list of event * @param cachedRecentMessages list of newly created source contacts or already existed but * updated with corresponding event object * @param isStatusChanged whether provider status changed and we are processing */ private void processEventObjects( Collection<EventObject> res, List<ComparableEvtObj> cachedRecentMessages, boolean isStatusChanged) { for (EventObject obj : res) { ComparableEvtObj oldMsg = findRecentMessage(obj, recentMessages); if (oldMsg != null) { oldMsg.update(obj); // update if (isStatusChanged && recentQuery != null) recentQuery.updateCapabilities(oldMsg, obj); // we still add it to cachedRecentMessages // later we will find it is duplicate and will fire // update event if (!cachedRecentMessages.contains(oldMsg)) cachedRecentMessages.add(oldMsg); continue; } oldMsg = findRecentMessage(obj, cachedRecentMessages); if (oldMsg == null) { oldMsg = new ComparableEvtObj(obj); if (isStatusChanged && recentQuery != null) recentQuery.updateCapabilities(oldMsg, obj); cachedRecentMessages.add(oldMsg); } } } /** * Access for source contacts impl. * * @return */ boolean isSMSEnabled() { return isSMSEnabled; } /** * Add the ComparableEvtObj, newly added will fire new, for existing fire update and when trimming * the list to desired length fire remove for those that were removed * * @param contactsToAdd */ private void addNewRecentMessages(List<ComparableEvtObj> contactsToAdd) { // now find object to fire new, and object to fire remove // let us find duplicates and fire update List<ComparableEvtObj> duplicates = new ArrayList<ComparableEvtObj>(); for (ComparableEvtObj msgToAdd : contactsToAdd) { if (recentMessages.contains(msgToAdd)) { duplicates.add(msgToAdd); // save update updateRecentMessageToHistory(msgToAdd); } } recentMessages.removeAll(duplicates); // now contacts to add has no duplicates, add them all boolean changed = recentMessages.addAll(contactsToAdd); if (changed) { Collections.sort(recentMessages); if (recentQuery != null) { for (ComparableEvtObj obj : duplicates) recentQuery.updateContact(obj, obj.getEventObject()); } } if (!recentMessages.isEmpty()) oldestRecentMessage = recentMessages.get(recentMessages.size() - 1).getTimestamp(); // trim List<ComparableEvtObj> removedItems = null; if (recentMessages.size() > numberOfMessages) { removedItems = new ArrayList<ComparableEvtObj>( recentMessages.subList(numberOfMessages, recentMessages.size())); recentMessages.removeAll(removedItems); } if (recentQuery != null) { // now fire, removed for all that were in the list // and now are removed after trim if (removedItems != null) { for (ComparableEvtObj msc : removedItems) { if (!contactsToAdd.contains(msc)) recentQuery.fireContactRemoved(msc); } } // fire new for all that were added, and not removed after trim for (ComparableEvtObj msc : contactsToAdd) { if ((removedItems == null || !removedItems.contains(msc)) && !duplicates.contains(msc)) { MessageSourceContact newSourceContact = new MessageSourceContact(msc.getEventObject(), MessageSourceService.this); newSourceContact.initDetails(msc.getEventObject()); recentQuery.addQueryResult(newSourceContact); } } // if recent messages were changed, indexes have change lets // fire event for the last element which will reorder the whole // group if needed. if (changed) recentQuery.fireContactChanged(recentMessages.get(recentMessages.size() - 1)); } } /** * When a provider is added, do not block and start executing in new thread. * * @param provider ProtocolProviderService */ void handleProviderAdded(final ProtocolProviderService provider, final boolean isStatusChanged) { new Thread( new Runnable() { @Override public void run() { handleProviderAddedInSeparateThread(provider, isStatusChanged); } }) .start(); } /** * When a provider is added. As searching can be slow especially when handling special type of * messages (with subType) this need to be run in new Thread. * * @param provider ProtocolProviderService */ private void handleProviderAddedInSeparateThread( ProtocolProviderService provider, boolean isStatusChanged) { // lets check if we have cached recent messages for this provider, and // fire events if found and are newer synchronized (recentMessages) { List<ComparableEvtObj> cachedRecentMessages = getCachedRecentMessages(provider, isStatusChanged); if (cachedRecentMessages.isEmpty()) { // maybe there is no cached history for this // let's check // load it not from cache, but do a local search Collection<EventObject> res = messageHistoryService.findRecentMessagesPerContact( numberOfMessages, provider.getAccountID().getAccountUniqueID(), null, isSMSEnabled); List<ComparableEvtObj> newMsc = new ArrayList<ComparableEvtObj>(); processEventObjects(res, newMsc, isStatusChanged); addNewRecentMessages(newMsc); for (ComparableEvtObj msc : newMsc) { saveRecentMessageToHistory(msc); } } else addNewRecentMessages(cachedRecentMessages); } } /** * Tries to match the event object to already existing ComparableEvtObj in the supplied list. * * @param obj the object that we will try to match. * @param list the list we will search in. * @return the found ComparableEvtObj */ private static ComparableEvtObj findRecentMessage(EventObject obj, List<ComparableEvtObj> list) { Contact contact = null; ChatRoom chatRoom = null; if (obj instanceof MessageDeliveredEvent) { contact = ((MessageDeliveredEvent) obj).getDestinationContact(); } else if (obj instanceof MessageReceivedEvent) { contact = ((MessageReceivedEvent) obj).getSourceContact(); } else if (obj instanceof ChatRoomMessageDeliveredEvent) { chatRoom = ((ChatRoomMessageDeliveredEvent) obj).getSourceChatRoom(); } else if (obj instanceof ChatRoomMessageReceivedEvent) { chatRoom = ((ChatRoomMessageReceivedEvent) obj).getSourceChatRoom(); } for (ComparableEvtObj evt : list) { if ((contact != null && contact.equals(evt.getContact())) || (chatRoom != null && chatRoom.equals(evt.getRoom()))) return evt; } return null; } /** * A provider has been removed. * * @param provider the ProtocolProviderService that has been unregistered. */ void handleProviderRemoved(ProtocolProviderService provider) { // lets remove the recent messages for this provider, and update // with recent messages for the available providers synchronized (recentMessages) { if (provider != null) { List<ComparableEvtObj> removedItems = new ArrayList<ComparableEvtObj>(); for (ComparableEvtObj msc : recentMessages) { if (msc.getProtocolProviderService().equals(provider)) removedItems.add(msc); } recentMessages.removeAll(removedItems); if (!recentMessages.isEmpty()) oldestRecentMessage = recentMessages.get(recentMessages.size() - 1).getTimestamp(); else oldestRecentMessage = null; if (recentQuery != null) { for (ComparableEvtObj msc : removedItems) { recentQuery.fireContactRemoved(msc); } } } // handleProviderRemoved can be invoked due to stopped // history service, if this is the case we do not want to // update messages if (!this.messageHistoryService.isHistoryLoggingEnabled()) return; // lets do the same as we enable provider // for all registered providers and finally fire events List<ComparableEvtObj> contactsToAdd = new ArrayList<ComparableEvtObj>(); for (ProtocolProviderService pps : messageHistoryService.getCurrentlyAvailableProviders()) { contactsToAdd.addAll(getCachedRecentMessages(pps, true)); } addNewRecentMessages(contactsToAdd); } } /** * Searches for contact ids in history of recent messages. * * @param provider * @param after * @return */ List<String> getRecentContactIDs(String provider, Date after) { List<String> res = new ArrayList<String>(); try { History history = getHistory(); if (history != null) { Iterator<HistoryRecord> recs = history.getReader().findLast(NUMBER_OF_MSGS_IN_HISTORY); SimpleDateFormat sdf = new SimpleDateFormat(HistoryService.DATE_FORMAT); while (recs.hasNext()) { HistoryRecord hr = recs.next(); String contact = null; String recordProvider = null; Date timestamp = null; for (int i = 0; i < hr.getPropertyNames().length; i++) { String propName = hr.getPropertyNames()[i]; if (propName.equals(STRUCTURE_NAMES[0])) recordProvider = hr.getPropertyValues()[i]; else if (propName.equals(STRUCTURE_NAMES[1])) contact = hr.getPropertyValues()[i]; else if (propName.equals(STRUCTURE_NAMES[2])) { try { timestamp = sdf.parse(hr.getPropertyValues()[i]); } catch (ParseException e) { timestamp = new Date(Long.parseLong(hr.getPropertyValues()[i])); } } } if (recordProvider == null || contact == null) continue; if (after != null && timestamp != null && timestamp.before(after)) continue; if (recordProvider.equals(provider)) res.add(contact); } } } catch (IOException ex) { logger.error("cannot create recent_messages history", ex); } return res; } /** * Returns the cached recent messages history. * * @return * @throws IOException */ private History getHistory() throws IOException { synchronized (historyID) { HistoryService historyService = MessageHistoryActivator.getMessageHistoryService().getHistoryService(); if (history == null) { history = historyService.createHistory(historyID, recordStructure); // lets check the version if not our version, re-create // history (delete it) HistoryReader reader = history.getReader(); boolean delete = false; QueryResultSet<HistoryRecord> res = reader.findLast(1); if (res != null && res.hasNext()) { HistoryRecord hr = res.next(); if (hr.getPropertyValues().length >= 4) { if (!hr.getPropertyValues()[3].equals(RECENT_MSGS_VER)) delete = true; } else delete = true; } if (delete) { // delete it try { historyService.purgeLocallyStoredHistory(historyID); history = historyService.createHistory(historyID, recordStructure); } catch (IOException ex) { logger.error("Cannot delete recent_messages history", ex); } } } return history; } } /** * Returns the index of the source contact, in the list of recent messages. * * @param messageSourceContact * @return */ int getIndex(MessageSourceContact messageSourceContact) { synchronized (recentMessages) { for (int i = 0; i < recentMessages.size(); i++) if (recentMessages.get(i).equals(messageSourceContact)) return i; return -1; } } /** * Creates query for the given <tt>searchString</tt>. * * @param queryString the string to search for * @param contactCount the maximum count of result contacts * @return the created query */ @Override public ContactQuery createContactQuery(String queryString, int contactCount) { if (!StringUtils.isNullOrEmpty(queryString)) return null; recentQuery = new MessageSourceContactQuery(MessageSourceService.this); return recentQuery; } /** * Updates contact source contacts with status. * * @param evt the ContactPresenceStatusChangeEvent describing the status */ @Override public void contactPresenceStatusChanged(ContactPresenceStatusChangeEvent evt) { if (recentQuery == null) return; synchronized (recentMessages) { for (ComparableEvtObj msg : recentMessages) { if (msg.getContact() != null && msg.getContact().equals(evt.getSourceContact())) { recentQuery.updateContactStatus(msg, evt.getNewStatus()); } } } } @Override public void providerStatusChanged(ProviderPresenceStatusChangeEvent evt) { if (!evt.getNewStatus().isOnline() || evt.getOldStatus().isOnline()) return; handleProviderAdded(evt.getProvider(), true); } @Override public void providerStatusMessageChanged(PropertyChangeEvent evt) {} @Override public void localUserPresenceChanged(LocalUserChatRoomPresenceChangeEvent evt) { if (recentQuery == null) return; ComparableEvtObj srcContact = null; synchronized (recentMessages) { for (ComparableEvtObj msg : recentMessages) { if (msg.getRoom() != null && msg.getRoom().equals(evt.getChatRoom())) { srcContact = msg; break; } } } if (srcContact == null) return; String eventType = evt.getEventType(); if (LocalUserChatRoomPresenceChangeEvent.LOCAL_USER_JOINED.equals(eventType)) { recentQuery.updateContactStatus(srcContact, ChatRoomPresenceStatus.CHAT_ROOM_ONLINE); } else if ((LocalUserChatRoomPresenceChangeEvent.LOCAL_USER_LEFT.equals(eventType) || LocalUserChatRoomPresenceChangeEvent.LOCAL_USER_KICKED.equals(eventType) || LocalUserChatRoomPresenceChangeEvent.LOCAL_USER_DROPPED.equals(eventType))) { recentQuery.updateContactStatus(srcContact, ChatRoomPresenceStatus.CHAT_ROOM_OFFLINE); } } /** * Handles new events. * * @param obj the event object * @param provider the provider * @param id the id of the source of the event */ private void handle(EventObject obj, ProtocolProviderService provider, String id) { // check if provider - contact exist update message content synchronized (recentMessages) { ComparableEvtObj existingMsc = null; for (ComparableEvtObj msc : recentMessages) { if (msc.getProtocolProviderService().equals(provider) && msc.getContactAddress().equals(id)) { // update msc.update(obj); updateRecentMessageToHistory(msc); existingMsc = msc; } } if (existingMsc != null) { Collections.sort(recentMessages); oldestRecentMessage = recentMessages.get(recentMessages.size() - 1).getTimestamp(); if (recentQuery != null) { recentQuery.updateContact(existingMsc, existingMsc.getEventObject()); recentQuery.fireContactChanged(existingMsc); } return; } // if missing create source contact // and update recent messages, trim and sort MessageSourceContact newSourceContact = new MessageSourceContact(obj, MessageSourceService.this); newSourceContact.initDetails(obj); // we have already checked for duplicate ComparableEvtObj newMsg = new ComparableEvtObj(obj); recentMessages.add(newMsg); Collections.sort(recentMessages); oldestRecentMessage = recentMessages.get(recentMessages.size() - 1).getTimestamp(); // trim List<ComparableEvtObj> removedItems = null; if (recentMessages.size() > numberOfMessages) { removedItems = new ArrayList<ComparableEvtObj>( recentMessages.subList(numberOfMessages, recentMessages.size())); recentMessages.removeAll(removedItems); } // save saveRecentMessageToHistory(newMsg); // no query nothing to fire if (recentQuery == null) return; // now fire if (removedItems != null) { for (ComparableEvtObj msc : removedItems) { recentQuery.fireContactRemoved(msc); } } recentQuery.addQueryResult(newSourceContact); } } /** Adds recent message in history. */ private void saveRecentMessageToHistory(ComparableEvtObj msc) { synchronized (historyID) { // and create it try { History history = getHistory(); HistoryWriter writer = history.getWriter(); SimpleDateFormat sdf = new SimpleDateFormat(HistoryService.DATE_FORMAT); writer.addRecord( new String[] { msc.getProtocolProviderService().getAccountID().getAccountUniqueID(), msc.getContactAddress(), sdf.format(msc.getTimestamp()), RECENT_MSGS_VER }, NUMBER_OF_MSGS_IN_HISTORY); } catch (IOException ex) { logger.error("cannot create recent_messages history", ex); return; } } } /** Updates recent message in history. */ private void updateRecentMessageToHistory(final ComparableEvtObj msg) { synchronized (historyID) { // and create it try { History history = getHistory(); HistoryWriter writer = history.getWriter(); writer.updateRecord( new HistoryWriter.HistoryRecordUpdater() { HistoryRecord hr; @Override public void setHistoryRecord(HistoryRecord historyRecord) { this.hr = historyRecord; } @Override public boolean isMatching() { boolean providerFound = false; boolean contactFound = false; for (int i = 0; i < hr.getPropertyNames().length; i++) { String propName = hr.getPropertyNames()[i]; if (propName.equals(STRUCTURE_NAMES[0])) { if (msg.getProtocolProviderService() .getAccountID() .getAccountUniqueID() .equals(hr.getPropertyValues()[i])) { providerFound = true; } } else if (propName.equals(STRUCTURE_NAMES[1])) { if (msg.getContactAddress().equals(hr.getPropertyValues()[i])) { contactFound = true; } } } return contactFound && providerFound; } @Override public Map<String, String> getUpdateChanges() { HashMap<String, String> map = new HashMap<String, String>(); SimpleDateFormat sdf = new SimpleDateFormat(HistoryService.DATE_FORMAT); for (int i = 0; i < hr.getPropertyNames().length; i++) { String propName = hr.getPropertyNames()[i]; if (propName.equals(STRUCTURE_NAMES[0])) { map.put( propName, msg.getProtocolProviderService().getAccountID().getAccountUniqueID()); } else if (propName.equals(STRUCTURE_NAMES[1])) { map.put(propName, msg.getContactAddress()); } else if (propName.equals(STRUCTURE_NAMES[2])) { map.put(propName, sdf.format(msg.getTimestamp())); } else if (propName.equals(STRUCTURE_NAMES[3])) map.put(propName, RECENT_MSGS_VER); } return map; } }); } catch (IOException ex) { logger.error("cannot create recent_messages history", ex); return; } } } @Override public void messageReceived(MessageReceivedEvent evt) { if (isSMSEnabled && evt.getEventType() != MessageReceivedEvent.SMS_MESSAGE_RECEIVED) { return; } handle(evt, evt.getSourceContact().getProtocolProvider(), evt.getSourceContact().getAddress()); } @Override public void messageDelivered(MessageDeliveredEvent evt) { if (isSMSEnabled && !evt.isSmsMessage()) return; handle( evt, evt.getDestinationContact().getProtocolProvider(), evt.getDestinationContact().getAddress()); } /** * Not used. * * @param evt the <tt>MessageFailedEvent</tt> containing the ID of the */ @Override public void messageDeliveryFailed(MessageDeliveryFailedEvent evt) {} @Override public void messageReceived(ChatRoomMessageReceivedEvent evt) { if (isSMSEnabled) return; // ignore non conversation messages if (evt.getEventType() != ChatRoomMessageReceivedEvent.CONVERSATION_MESSAGE_RECEIVED) return; handle( evt, evt.getSourceChatRoom().getParentProvider(), evt.getSourceChatRoom().getIdentifier()); } @Override public void messageDelivered(ChatRoomMessageDeliveredEvent evt) { if (isSMSEnabled) return; handle( evt, evt.getSourceChatRoom().getParentProvider(), evt.getSourceChatRoom().getIdentifier()); } /** * Not used. * * @param evt the <tt>ChatroomMessageDeliveryFailedEvent</tt> containing */ @Override public void messageDeliveryFailed(ChatRoomMessageDeliveryFailedEvent evt) {} @Override public void messageReceived(AdHocChatRoomMessageReceivedEvent evt) { // TODO } @Override public void messageDelivered(AdHocChatRoomMessageDeliveredEvent evt) { // TODO } /** * Not used. * * @param evt the <tt>AdHocChatroomMessageDeliveryFailedEvent</tt> */ @Override public void messageDeliveryFailed(AdHocChatRoomMessageDeliveryFailedEvent evt) {} @Override public void subscriptionCreated(SubscriptionEvent evt) {} @Override public void subscriptionFailed(SubscriptionEvent evt) {} @Override public void subscriptionRemoved(SubscriptionEvent evt) {} @Override public void subscriptionMoved(SubscriptionMovedEvent evt) {} @Override public void subscriptionResolved(SubscriptionEvent evt) {} /** * If a contact is renamed update the locally stored message if any. * * @param evt the <tt>ContactPropertyChangeEvent</tt> containing the source */ @Override public void contactModified(ContactPropertyChangeEvent evt) { if (!evt.getPropertyName().equals(ContactPropertyChangeEvent.PROPERTY_DISPLAY_NAME)) return; Contact contact = evt.getSourceContact(); if (contact == null) return; for (ComparableEvtObj msc : recentMessages) { if (contact.equals(msc.getContact())) { if (recentQuery != null) recentQuery.updateContactDisplayName(msc, contact.getDisplayName()); return; } } } /** * Indicates that a MetaContact has been modified. * * @param evt the MetaContactListEvent containing the corresponding contact */ public void metaContactRenamed(MetaContactRenamedEvent evt) { for (ComparableEvtObj msc : recentMessages) { if (evt.getSourceMetaContact().containsContact(msc.getContact())) { if (recentQuery != null) recentQuery.updateContactDisplayName(msc, evt.getNewDisplayName()); } } } @Override public void supportedOperationSetsChanged(ContactCapabilitiesEvent event) { Contact contact = event.getSourceContact(); if (contact == null) return; for (ComparableEvtObj msc : recentMessages) { if (contact.equals(msc.getContact())) { if (recentQuery != null) recentQuery.updateCapabilities(msc, contact); return; } } } /** Permanently removes all locally stored message history, remove recent contacts. */ public void eraseLocallyStoredHistory() throws IOException { List<ComparableEvtObj> toRemove = null; synchronized (recentMessages) { toRemove = new ArrayList<ComparableEvtObj>(recentMessages); recentMessages.clear(); } if (recentQuery != null) { for (ComparableEvtObj msc : toRemove) { recentQuery.fireContactRemoved(msc); } } } /** * Permanently removes locally stored message history for the metacontact, remove any recent * contacts if any. */ public void eraseLocallyStoredHistory(MetaContact contact) throws IOException { List<ComparableEvtObj> toRemove = null; synchronized (recentMessages) { toRemove = new ArrayList<ComparableEvtObj>(); Iterator<Contact> iter = contact.getContacts(); while (iter.hasNext()) { Contact item = iter.next(); String id = item.getAddress(); ProtocolProviderService provider = item.getProtocolProvider(); for (ComparableEvtObj msc : recentMessages) { if (msc.getProtocolProviderService().equals(provider) && msc.getContactAddress().equals(id)) { toRemove.add(msc); } } } recentMessages.removeAll(toRemove); } if (recentQuery != null) { for (ComparableEvtObj msc : toRemove) { recentQuery.fireContactRemoved(msc); } } } /** * Permanently removes locally stored message history for the chatroom, remove any recent contacts * if any. */ public void eraseLocallyStoredHistory(ChatRoom room) { ComparableEvtObj toRemove = null; synchronized (recentMessages) { for (ComparableEvtObj msg : recentMessages) { if (msg.getRoom() != null && msg.getRoom().equals(room)) { toRemove = msg; break; } } if (toRemove == null) return; recentMessages.remove(toRemove); } if (recentQuery != null) recentQuery.fireContactRemoved(toRemove); } /** Object used to cache recent messages. */ private class ComparableEvtObj implements Comparable<ComparableEvtObj> { private EventObject eventObject; /** The protocol provider. */ private ProtocolProviderService ppService = null; /** The address. */ private String address = null; /** The timestamp. */ private Date timestamp = null; /** The contact instance. */ private Contact contact = null; /** The room instance. */ private ChatRoom room = null; /** * Constructs. * * @param source used to extract initial values. */ ComparableEvtObj(EventObject source) { update(source); } /** * Extract values from <tt>EventObject</tt>. * * @param source */ public void update(EventObject source) { this.eventObject = source; if (source instanceof MessageDeliveredEvent) { MessageDeliveredEvent e = (MessageDeliveredEvent) source; this.contact = e.getDestinationContact(); this.address = contact.getAddress(); this.ppService = contact.getProtocolProvider(); this.timestamp = e.getTimestamp(); } else if (source instanceof MessageReceivedEvent) { MessageReceivedEvent e = (MessageReceivedEvent) source; this.contact = e.getSourceContact(); this.address = contact.getAddress(); this.ppService = contact.getProtocolProvider(); this.timestamp = e.getTimestamp(); } else if (source instanceof ChatRoomMessageDeliveredEvent) { ChatRoomMessageDeliveredEvent e = (ChatRoomMessageDeliveredEvent) source; this.room = e.getSourceChatRoom(); this.address = room.getIdentifier(); this.ppService = room.getParentProvider(); this.timestamp = e.getTimestamp(); } else if (source instanceof ChatRoomMessageReceivedEvent) { ChatRoomMessageReceivedEvent e = (ChatRoomMessageReceivedEvent) source; this.room = e.getSourceChatRoom(); this.address = room.getIdentifier(); this.ppService = room.getParentProvider(); this.timestamp = e.getTimestamp(); } } @Override public String toString() { return "ComparableEvtObj{" + "address='" + address + '\'' + ", ppService=" + ppService + '}'; } /** * The timestamp of the message. * * @return the timestamp of the message. */ public Date getTimestamp() { return timestamp; } /** * The contact. * * @return the contact. */ public Contact getContact() { return contact; } /** * The room. * * @return the room. */ public ChatRoom getRoom() { return room; } /** * The protocol provider. * * @return the protocol provider. */ public ProtocolProviderService getProtocolProviderService() { return ppService; } /** * The address. * * @return the address. */ public String getContactAddress() { if (this.address != null) return this.address; return null; } /** * The event object. * * @return the event object. */ public EventObject getEventObject() { return eventObject; } /** * Compares two ComparableEvtObj. * * @param o the object to compare with * @return 0, less than zero, greater than zero, if equals, less or greater. */ @Override public int compareTo(ComparableEvtObj o) { if (o == null || o.getTimestamp() == null) return 1; return o.getTimestamp().compareTo(getTimestamp()); } /** * Checks if equals, and if this event object is used to create a MessageSourceContact, if the * supplied <tt>Object</tt> is instance of MessageSourceContact. * * @param o the object to check. * @return <tt>true</tt> if equals. */ @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || (!(o instanceof MessageSourceContact) && getClass() != o.getClass())) return false; if (o instanceof ComparableEvtObj) { ComparableEvtObj that = (ComparableEvtObj) o; if (!address.equals(that.address)) return false; if (!ppService.equals(that.ppService)) return false; } else if (o instanceof MessageSourceContact) { MessageSourceContact that = (MessageSourceContact) o; if (!address.equals(that.getContactAddress())) return false; if (!ppService.equals(that.getProtocolProviderService())) return false; } else return false; return true; } @Override public int hashCode() { int result = address.hashCode(); result = 31 * result + ppService.hashCode(); return result; } } }
/** * The <tt>CallManager</tt> is the one that handles calls. It contains also the "Call" and "Hangup" * buttons panel. Here are handles incoming and outgoing calls from and to the call operation set. * * @author Yana Stamcheva */ public class CallManager extends JPanel implements ActionListener, CallListener, ListSelectionListener, ChangeListener { private Logger logger = Logger.getLogger(CallManager.class.getName()); private CallComboBox phoneNumberCombo; private JPanel comboPanel = new JPanel(new BorderLayout()); private JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 0)); private JLabel callViaLabel = new JLabel(Messages.getI18NString("callVia").getText() + " "); private JPanel callViaPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 0, 4)); private AccountSelectorBox accountSelectorBox; private SIPCommButton callButton = new SIPCommButton( ImageLoader.getImage(ImageLoader.CALL_BUTTON_BG), ImageLoader.getImage(ImageLoader.CALL_ROLLOVER_BUTTON_BG), null, ImageLoader.getImage(ImageLoader.CALL_BUTTON_PRESSED_BG)); private SIPCommButton hangupButton = new SIPCommButton( ImageLoader.getImage(ImageLoader.HANGUP_BUTTON_BG), ImageLoader.getImage(ImageLoader.HANGUP_ROLLOVER_BUTTON_BG), null, ImageLoader.getImage(ImageLoader.HANGUP_BUTTON_PRESSED_BG)); private SIPCommButton minimizeButton = new SIPCommButton( ImageLoader.getImage(ImageLoader.CALL_PANEL_MINIMIZE_BUTTON), ImageLoader.getImage(ImageLoader.CALL_PANEL_MINIMIZE_ROLLOVER_BUTTON)); private SIPCommButton restoreButton = new SIPCommButton( ImageLoader.getImage(ImageLoader.CALL_PANEL_RESTORE_BUTTON), ImageLoader.getImage(ImageLoader.CALL_PANEL_RESTORE_ROLLOVER_BUTTON)); private JPanel minimizeButtonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); private MainFrame mainFrame; private Hashtable activeCalls = new Hashtable(); private boolean isCallMetaContact; private Hashtable removeCallTimers = new Hashtable(); private ProtocolProviderService selectedCallProvider; /** * Creates an instance of <tt>CallManager</tt>. * * @param mainFrame The main application window. */ public CallManager(MainFrame mainFrame) { super(new BorderLayout()); this.mainFrame = mainFrame; this.phoneNumberCombo = new CallComboBox(this); this.accountSelectorBox = new AccountSelectorBox(this); this.buttonsPanel.setBorder(BorderFactory.createEmptyBorder(5, 0, 0, 0)); this.comboPanel.setBorder(BorderFactory.createEmptyBorder(10, 5, 0, 5)); this.init(); } /** Initializes and constructs this panel. */ private void init() { this.phoneNumberCombo.setEditable(true); this.callViaPanel.add(callViaLabel); this.callViaPanel.add(accountSelectorBox); this.comboPanel.add(phoneNumberCombo, BorderLayout.CENTER); this.callButton.setName("call"); this.hangupButton.setName("hangup"); this.minimizeButton.setName("minimize"); this.restoreButton.setName("restore"); this.minimizeButton.setToolTipText( Messages.getI18NString("hideCallPanel").getText() + " Ctrl - H"); this.restoreButton.setToolTipText( Messages.getI18NString("showCallPanel").getText() + " Ctrl - H"); this.callButton.addActionListener(this); this.hangupButton.addActionListener(this); this.minimizeButton.addActionListener(this); this.restoreButton.addActionListener(this); this.buttonsPanel.add(callButton); this.buttonsPanel.add(hangupButton); this.callButton.setEnabled(false); this.hangupButton.setEnabled(false); this.add(minimizeButtonPanel, BorderLayout.SOUTH); this.setCallPanelVisible(ConfigurationManager.isCallPanelShown()); } /** * Handles the <tt>ActionEvent</tt> generated when user presses one of the buttons in this panel. */ public void actionPerformed(ActionEvent evt) { JButton button = (JButton) evt.getSource(); String buttonName = button.getName(); if (buttonName.equals("call")) { Component selectedPanel = mainFrame.getSelectedTab(); // call button is pressed over an already open call panel if (selectedPanel != null && selectedPanel instanceof CallPanel && ((CallPanel) selectedPanel).getCall().getCallState() == CallState.CALL_INITIALIZATION) { NotificationManager.stopSound(NotificationManager.BUSY_CALL); NotificationManager.stopSound(NotificationManager.INCOMING_CALL); CallPanel callPanel = (CallPanel) selectedPanel; Iterator participantPanels = callPanel.getParticipantsPanels(); while (participantPanels.hasNext()) { CallParticipantPanel panel = (CallParticipantPanel) participantPanels.next(); panel.setState("Connecting"); } Call call = callPanel.getCall(); answerCall(call); } // call button is pressed over the call list else if (selectedPanel != null && selectedPanel instanceof CallListPanel && ((CallListPanel) selectedPanel).getCallList().getSelectedIndex() != -1) { CallListPanel callListPanel = (CallListPanel) selectedPanel; GuiCallParticipantRecord callRecord = (GuiCallParticipantRecord) callListPanel.getCallList().getSelectedValue(); String stringContact = callRecord.getParticipantName(); createCall(stringContact); } // call button is pressed over the contact list else if (selectedPanel != null && selectedPanel instanceof ContactListPanel) { // call button is pressed when a meta contact is selected if (isCallMetaContact) { Object[] selectedContacts = mainFrame.getContactListPanel().getContactList().getSelectedValues(); Vector telephonyContacts = new Vector(); for (int i = 0; i < selectedContacts.length; i++) { Object o = selectedContacts[i]; if (o instanceof MetaContact) { Contact contact = ((MetaContact) o).getDefaultContact(OperationSetBasicTelephony.class); if (contact != null) telephonyContacts.add(contact); else { new ErrorDialog( this.mainFrame, Messages.getI18NString("warning").getText(), Messages.getI18NString( "contactNotSupportingTelephony", new String[] {((MetaContact) o).getDisplayName()}) .getText()) .showDialog(); } } } if (telephonyContacts.size() > 0) createCall(telephonyContacts); } else if (!phoneNumberCombo.isComboFieldEmpty()) { // if no contact is selected checks if the user has chosen // or has // writen something in the phone combo box String stringContact = phoneNumberCombo.getEditor().getItem().toString(); createCall(stringContact); } } else if (selectedPanel != null && selectedPanel instanceof DialPanel) { String stringContact = phoneNumberCombo.getEditor().getItem().toString(); createCall(stringContact); } } else if (buttonName.equalsIgnoreCase("hangup")) { Component selectedPanel = this.mainFrame.getSelectedTab(); if (selectedPanel != null && selectedPanel instanceof CallPanel) { NotificationManager.stopSound(NotificationManager.BUSY_CALL); NotificationManager.stopSound(NotificationManager.INCOMING_CALL); NotificationManager.stopSound(NotificationManager.OUTGOING_CALL); CallPanel callPanel = (CallPanel) selectedPanel; Call call = callPanel.getCall(); if (removeCallTimers.containsKey(callPanel)) { ((Timer) removeCallTimers.get(callPanel)).stop(); removeCallTimers.remove(callPanel); } removeCallPanel(callPanel); if (call != null) { ProtocolProviderService pps = call.getProtocolProvider(); OperationSetBasicTelephony telephony = mainFrame.getTelephonyOpSet(pps); Iterator participants = call.getCallParticipants(); while (participants.hasNext()) { try { // now we hang up the first call participant in the // call telephony.hangupCallParticipant((CallParticipant) participants.next()); } catch (OperationFailedException e) { logger.error("Hang up was not successful: " + e); } } } } } else if (buttonName.equalsIgnoreCase("minimize")) { JCheckBoxMenuItem hideCallPanelItem = mainFrame.getMainMenu().getViewMenu().getHideCallPanelItem(); if (!hideCallPanelItem.isSelected()) hideCallPanelItem.setSelected(true); this.setCallPanelVisible(false); } else if (buttonName.equalsIgnoreCase("restore")) { JCheckBoxMenuItem hideCallPanelItem = mainFrame.getMainMenu().getViewMenu().getHideCallPanelItem(); if (hideCallPanelItem.isSelected()) hideCallPanelItem.setSelected(false); this.setCallPanelVisible(true); } } /** Hides the panel containing call and hangup buttons. */ public void setCallPanelVisible(boolean isVisible) { if (isVisible) { this.add(comboPanel, BorderLayout.NORTH); this.add(buttonsPanel, BorderLayout.CENTER); this.minimizeButtonPanel.removeAll(); this.minimizeButtonPanel.add(minimizeButton); } else { this.remove(comboPanel); this.remove(buttonsPanel); this.minimizeButtonPanel.removeAll(); this.minimizeButtonPanel.add(restoreButton); if (mainFrame.isVisible()) this.mainFrame.getContactListPanel().getContactList().requestFocus(); } if (ConfigurationManager.isCallPanelShown() != isVisible) ConfigurationManager.setShowCallPanel(isVisible); this.mainFrame.validate(); } /** * Adds the given call account to the list of call via accounts. * * @param pps the protocol provider service corresponding to the account */ public void addCallAccount(ProtocolProviderService pps) { if (accountSelectorBox.getAccountsNumber() > 0) { this.comboPanel.add(callViaPanel, BorderLayout.SOUTH); } accountSelectorBox.addAccount(pps); } /** * Removes the account corresponding to the given protocol provider from the call via selector * box. * * @param pps the protocol provider service to remove */ public void removeCallAccount(ProtocolProviderService pps) { this.accountSelectorBox.removeAccount(pps); if (accountSelectorBox.getAccountsNumber() < 2) { this.comboPanel.remove(callViaPanel); } } /** * Returns TRUE if the account corresponding to the given protocol provider is already contained * in the call via selector box, otherwise returns FALSE. * * @param pps the protocol provider service for the account * @return TRUE if the account corresponding to the given protocol provider is already contained * in the call via selector box, otherwise returns FALSE */ public boolean containsCallAccount(ProtocolProviderService pps) { return accountSelectorBox.containsAccount(pps); } /** * Updates the call via account status. * * @param pps the protocol provider service for the account */ public void updateCallAccountStatus(ProtocolProviderService pps) { accountSelectorBox.updateAccountStatus(pps); } /** * Returns the account selector box. * * @return the account selector box. */ public AccountSelectorBox getAccountSelectorBox() { return accountSelectorBox; } /** * Sets the protocol provider to use for a call. * * @param provider the protocol provider to use for a call. */ public void setCallProvider(ProtocolProviderService provider) { this.selectedCallProvider = provider; } /** * Implements CallListener.incomingCallReceived. When a call is received creates a call panel and * adds it to the main tabbed pane and plays the ring phone sound to the user. */ public void incomingCallReceived(CallEvent event) { Call sourceCall = event.getSourceCall(); CallPanel callPanel = new CallPanel(this, sourceCall, GuiCallParticipantRecord.INCOMING_CALL); mainFrame.addCallPanel(callPanel); if (mainFrame.getState() == JFrame.ICONIFIED) mainFrame.setState(JFrame.NORMAL); if (!mainFrame.isVisible()) mainFrame.setVisible(true); mainFrame.toFront(); this.callButton.setEnabled(true); this.hangupButton.setEnabled(true); NotificationManager.fireNotification( NotificationManager.INCOMING_CALL, null, "Incoming call recived from: " + sourceCall.getCallParticipants().next()); activeCalls.put(sourceCall, callPanel); this.setCallPanelVisible(true); } /** * Implements CallListener.callEnded. Stops sounds that are playing at the moment if there're any. * Removes the call panel and disables the hangup button. */ public void callEnded(CallEvent event) { Call sourceCall = event.getSourceCall(); NotificationManager.stopSound(NotificationManager.BUSY_CALL); NotificationManager.stopSound(NotificationManager.INCOMING_CALL); NotificationManager.stopSound(NotificationManager.OUTGOING_CALL); if (activeCalls.get(sourceCall) != null) { CallPanel callPanel = (CallPanel) activeCalls.get(sourceCall); this.removeCallPanelWait(callPanel); } } public void outgoingCallCreated(CallEvent event) {} /** * Removes the given call panel tab. * * @param callPanel the CallPanel to remove */ public void removeCallPanelWait(CallPanel callPanel) { Timer timer = new Timer(5000, new RemoveCallPanelListener(callPanel)); this.removeCallTimers.put(callPanel, timer); timer.setRepeats(false); timer.start(); } /** * Removes the given call panel tab. * * @param callPanel the CallPanel to remove */ private void removeCallPanel(CallPanel callPanel) { if (callPanel.getCall() != null && activeCalls.contains(callPanel.getCall())) { this.activeCalls.remove(callPanel.getCall()); } mainFrame.removeCallPanel(callPanel); updateButtonsStateAccordingToSelectedPanel(); } /** Removes the given CallPanel from the main tabbed pane. */ private class RemoveCallPanelListener implements ActionListener { private CallPanel callPanel; public RemoveCallPanelListener(CallPanel callPanel) { this.callPanel = callPanel; } public void actionPerformed(ActionEvent e) { removeCallPanel(callPanel); } } /** * Implements ListSelectionListener.valueChanged. Enables or disables call and hangup buttons * depending on the selection in the contactlist. */ public void valueChanged(ListSelectionEvent e) { Object o = mainFrame.getContactListPanel().getContactList().getSelectedValue(); if ((e.getFirstIndex() != -1 || e.getLastIndex() != -1) && (o instanceof MetaContact)) { setCallMetaContact(true); // Switch automatically to the appropriate pps in account selector // box and enable callButton if telephony is supported. Contact contact = ((MetaContact) o).getDefaultContact(OperationSetBasicTelephony.class); if (contact != null) { callButton.setEnabled(true); if (contact.getProtocolProvider().isRegistered()) getAccountSelectorBox().setSelected(contact.getProtocolProvider()); } else { callButton.setEnabled(false); } } else if (phoneNumberCombo.isComboFieldEmpty()) { callButton.setEnabled(false); } } /** * Implements ChangeListener.stateChanged. Enables the hangup button if ones selects a tab in the * main tabbed pane that contains a call panel. */ public void stateChanged(ChangeEvent e) { this.updateButtonsStateAccordingToSelectedPanel(); Component selectedPanel = mainFrame.getSelectedTab(); if (selectedPanel == null || !(selectedPanel instanceof CallPanel)) { Iterator callPanels = activeCalls.values().iterator(); while (callPanels.hasNext()) { CallPanel callPanel = (CallPanel) callPanels.next(); callPanel.removeDialogs(); } } } /** Updates call and hangup buttons' states aa */ private void updateButtonsStateAccordingToSelectedPanel() { Component selectedPanel = mainFrame.getSelectedTab(); if (selectedPanel != null && selectedPanel instanceof CallPanel) { this.hangupButton.setEnabled(true); } else { this.hangupButton.setEnabled(false); } } /** * Returns the call button. * * @return the call button */ public SIPCommButton getCallButton() { return callButton; } /** * Returns the hangup button. * * @return the hangup button */ public SIPCommButton getHangupButton() { return hangupButton; } /** * Returns the main application frame. Meant to be used from the contained components that do not * have direct access to the MainFrame. * * @return the main application frame */ public MainFrame getMainFrame() { return mainFrame; } /** * Returns the combo box, where user enters the phone number to call to. * * @return the combo box, where user enters the phone number to call to. */ public JComboBox getCallComboBox() { return phoneNumberCombo; } /** * Answers the given call. * * @param call the call to answer */ public void answerCall(Call call) { new AnswerCallThread(call).start(); } /** * Returns TRUE if this call is a call to an internal meta contact from the contact list, * otherwise returns FALSE. * * @return TRUE if this call is a call to an internal meta contact from the contact list, * otherwise returns FALSE */ public boolean isCallMetaContact() { return isCallMetaContact; } /** * Sets the isCallMetaContact variable to TRUE or FALSE. This defines if this call is a call to a * given meta contact selected from the contact list or a call to an external contact or phone * number. * * @param isCallMetaContact TRUE to define this call as a call to an internal meta contact and * FALSE to define it as a call to an external contact or phone number. */ public void setCallMetaContact(boolean isCallMetaContact) { this.isCallMetaContact = isCallMetaContact; } /** * Creates a call to the contact represented by the given string. * * @param contact the contact to call to */ public void createCall(String contact) { CallPanel callPanel = new CallPanel(this, contact); mainFrame.addCallPanel(callPanel); new CreateCallThread(contact, callPanel).start(); } /** * Creates a call to the given list of contacts. * * @param contacts the list of contacts to call to */ public void createCall(Vector contacts) { CallPanel callPanel = new CallPanel(this, contacts); mainFrame.addCallPanel(callPanel); new CreateCallThread(contacts, callPanel).start(); } /** Creates a call from a given Contact or a given String. */ private class CreateCallThread extends Thread { Vector contacts; CallPanel callPanel; String stringContact; OperationSetBasicTelephony telephony; public CreateCallThread(String contact, CallPanel callPanel) { this.stringContact = contact; this.callPanel = callPanel; if (selectedCallProvider != null) telephony = mainFrame.getTelephonyOpSet(selectedCallProvider); } public CreateCallThread(Vector contacts, CallPanel callPanel) { this.contacts = contacts; this.callPanel = callPanel; if (selectedCallProvider != null) telephony = mainFrame.getTelephonyOpSet(selectedCallProvider); } public void run() { if (telephony == null) return; Call createdCall = null; if (contacts != null) { Contact contact = (Contact) contacts.get(0); // NOTE: The multi user call is not yet implemented! // We just get the first contact and create a call for him. try { createdCall = telephony.createCall(contact); } catch (OperationFailedException e) { logger.error("The call could not be created: " + e); callPanel.getParticipantPanel(contact.getDisplayName()).setState(e.getMessage()); removeCallPanelWait(callPanel); } // If the call is successfully created we set the created // Call instance to the already existing CallPanel and we // add this call to the active calls. if (createdCall != null) { callPanel.setCall(createdCall, GuiCallParticipantRecord.OUTGOING_CALL); activeCalls.put(createdCall, callPanel); } } else { try { createdCall = telephony.createCall(stringContact); } catch (ParseException e) { logger.error("The call could not be created: " + e); callPanel.getParticipantPanel(stringContact).setState(e.getMessage()); removeCallPanelWait(callPanel); } catch (OperationFailedException e) { logger.error("The call could not be created: " + e); callPanel.getParticipantPanel(stringContact).setState(e.getMessage()); removeCallPanelWait(callPanel); } // If the call is successfully created we set the created // Call instance to the already existing CallPanel and we // add this call to the active calls. if (createdCall != null) { callPanel.setCall(createdCall, GuiCallParticipantRecord.OUTGOING_CALL); activeCalls.put(createdCall, callPanel); } } } } /** Answers all call participants in the given call. */ private class AnswerCallThread extends Thread { private Call call; public AnswerCallThread(Call call) { this.call = call; } public void run() { ProtocolProviderService pps = call.getProtocolProvider(); Iterator participants = call.getCallParticipants(); while (participants.hasNext()) { CallParticipant participant = (CallParticipant) participants.next(); OperationSetBasicTelephony telephony = mainFrame.getTelephonyOpSet(pps); try { telephony.answerCallParticipant(participant); } catch (OperationFailedException e) { logger.error( "Could not answer to : " + participant + " caused by the following exception: " + e); } } } } }
/** * This activity allows user to add new contacts. * * @author Pawel Domas */ public class AddContactActivity extends OSGiActivity { /** The logger. */ private static final Logger logger = Logger.getLogger(AddContactActivity.class); /** {@inheritDoc} */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.add_contact); setTitle(R.string.service_gui_ADD_CONTACT); initAccountSpinner(); initContactGroupSpinner(); } /** Initializes "select account" spinner with existing accounts. */ private void initAccountSpinner() { Spinner accountsSpiner = (Spinner) findViewById(R.id.selectAccountSpinner); Iterator<ProtocolProviderService> providers = AccountUtils.getRegisteredProviders().iterator(); List<AccountID> accounts = new ArrayList<AccountID>(); int selectedIdx = -1; int idx = 0; while (providers.hasNext()) { ProtocolProviderService provider = providers.next(); OperationSet opSet = provider.getOperationSet(OperationSetPresence.class); if (opSet == null) continue; AccountID account = provider.getAccountID(); accounts.add(account); idx++; if (account.isPreferredProvider()) { selectedIdx = idx; } } AccountsListAdapter accountsAdapter = new AccountsListAdapter( this, R.layout.select_account_row, R.layout.select_account_dropdown, accounts, true); accountsSpiner.setAdapter(accountsAdapter); // if we have only select account option and only one account // select the available account if (accounts.size() == 1) accountsSpiner.setSelection(0); else accountsSpiner.setSelection(selectedIdx); } /** Initializes select contact group spinner with contact groups. */ private void initContactGroupSpinner() { Spinner groupSpinner = (Spinner) findViewById(R.id.selectGroupSpinner); MetaContactGroupAdapter contactGroupAdapter = new MetaContactGroupAdapter(this, R.id.selectGroupSpinner, true, true); contactGroupAdapter.setItemLayout(R.layout.simple_spinner_item); contactGroupAdapter.setDropDownLayout(R.layout.dropdown_spinner_item); groupSpinner.setAdapter(contactGroupAdapter); } /** * Method fired when "add" button is clicked. * * @param v add button's <tt>View</tt> */ public void onAddClicked(View v) { Spinner accountsSpiner = (Spinner) findViewById(R.id.selectAccountSpinner); Account selectedAcc = (Account) accountsSpiner.getSelectedItem(); if (selectedAcc == null) { logger.error("No account selected"); return; } ProtocolProviderService pps = selectedAcc.getProtocolProvider(); if (pps == null) { logger.error("No provider registered for account " + selectedAcc.getAccountName()); return; } View content = findViewById(android.R.id.content); String contactAddress = ViewUtil.getTextViewValue(content, R.id.editContactName); String displayName = ViewUtil.getTextViewValue(content, R.id.editDisplayName); if (displayName != null && displayName.length() > 0) { addRenameListener(pps, null, contactAddress, displayName); } Spinner groupSpinner = (Spinner) findViewById(R.id.selectGroupSpinner); ContactListUtils.addContact( pps, (MetaContactGroup) groupSpinner.getSelectedItem(), contactAddress); finish(); } /** * Adds a rename listener. * * @param protocolProvider the protocol provider to which the contact was added * @param metaContact the <tt>MetaContact</tt> if the new contact was added to an existing meta * contact * @param contactAddress the address of the newly added contact * @param displayName the new display name */ private void addRenameListener( final ProtocolProviderService protocolProvider, final MetaContact metaContact, final String contactAddress, final String displayName) { AndroidGUIActivator.getContactListService() .addMetaContactListListener( new MetaContactListAdapter() { @Override public void metaContactAdded(MetaContactEvent evt) { if (evt.getSourceMetaContact().getContact(contactAddress, protocolProvider) != null) { renameContact(evt.getSourceMetaContact(), displayName); } } @Override public void protoContactAdded(ProtoContactEvent evt) { if (metaContact != null && evt.getNewParent().equals(metaContact)) { renameContact(metaContact, displayName); } } }); } /** * Renames the given meta contact. * * @param metaContact the <tt>MetaContact</tt> to rename * @param displayName the new display name */ private void renameContact(final MetaContact metaContact, final String displayName) { new Thread() { @Override public void run() { AndroidGUIActivator.getContactListService().renameMetaContact(metaContact, displayName); } }.start(); } }
/** * The <tt>PresenceFilter</tt> is used to filter offline contacts from the contact list. * * @author Yana Stamcheva */ public class PresenceFilter implements ContactListFilter { /** * The <tt>Logger</tt> used by the <tt>PresenceFilter</tt> class and its instances to print * debugging information. */ private static final Logger logger = Logger.getLogger(PresenceFilter.class); /** Indicates if this presence filter shows or hides the offline contacts. */ private boolean isShowOffline; /** * The initial result count below which we insert all filter results directly to the contact list * without firing events. */ private static final int INITIAL_CONTACT_COUNT = 30; /** * Preferences for the external contact sources. Lists the type of contact contact sources that * will be displayed in the filter and the order of the contact sources. */ private static Map<Integer, Integer> contactSourcePreferences = new HashMap<Integer, Integer>(); /** Creates an instance of <tt>PresenceFilter</tt>. */ public PresenceFilter() { isShowOffline = ConfigurationUtils.isShowOffline(); initContactSourcePreferences(); } /** * Initializes the contact source preferences. The preferences are for the visibility of the * contact source and their order. */ private void initContactSourcePreferences() { // This entry will be used to set the index for chat room contact sources // The index is used to order the contact sources in the contact list. // The chat room sources will be ordered before the meta contact list. contactSourcePreferences.put(ContactSourceService.CHAT_ROOM_TYPE, 0); } /** * Applies this filter. This filter is applied over the <tt>MetaContactListService</tt>. * * @param filterQuery the query which keeps track of the filtering results */ public void applyFilter(FilterQuery filterQuery) { // Create the query that will track filtering. MetaContactQuery query = new MetaContactQuery(); // Add this query to the filterQuery. filterQuery.addContactQuery(query); for (int cssType : contactSourcePreferences.keySet()) { Iterator<UIContactSource> filterSources = GuiActivator.getContactList().getContactSources(cssType).iterator(); while (filterSources.hasNext()) { UIContactSource filterSource = filterSources.next(); Integer prefValue = contactSourcePreferences.get(cssType); // We are setting the index from contactSourcePreferences map to // the contact source. This index is set to reorder the sources // in the contact list. if (prefValue != null) filterSource.setContactSourceIndex(prefValue); ContactSourceService sourceService = filterSource.getContactSourceService(); ContactQuery contactQuery = sourceService.createContactQuery(null); // Add this query to the filterQuery. filterQuery.addContactQuery(contactQuery); contactQuery.addContactQueryListener(GuiActivator.getContactList()); contactQuery.start(); } } // Closes this filter to indicate that we finished adding queries to it. filterQuery.close(); query.addContactQueryListener(GuiActivator.getContactList()); int resultCount = 0; addMatching(GuiActivator.getContactListService().getRoot(), query, resultCount); query.fireQueryEvent( query.isCanceled() ? MetaContactQueryStatusEvent.QUERY_CANCELED : MetaContactQueryStatusEvent.QUERY_COMPLETED); } /** * Indicates if the given <tt>uiContact</tt> is matching this filter. * * @param uiContact the <tt>UIContact</tt> to check * @return <tt>true</tt> if the given <tt>uiContact</tt> is matching this filter, otherwise * returns <tt>false</tt> */ public boolean isMatching(UIContact uiContact) { Object descriptor = uiContact.getDescriptor(); if (descriptor instanceof MetaContact) return isMatching((MetaContact) descriptor); else if (descriptor instanceof SourceContact) return isMatching((SourceContact) descriptor); else return false; } /** * Indicates if the given <tt>uiGroup</tt> is matching this filter. * * @param uiGroup the <tt>UIGroup</tt> to check * @return <tt>true</tt> if the given <tt>uiGroup</tt> is matching this filter, otherwise returns * <tt>false</tt> */ public boolean isMatching(UIGroup uiGroup) { Object descriptor = uiGroup.getDescriptor(); if (descriptor instanceof MetaContactGroup) return isMatching((MetaContactGroup) descriptor); else return false; } /** * Sets the show offline property. * * @param isShowOffline indicates if offline contacts are shown */ public void setShowOffline(boolean isShowOffline) { this.isShowOffline = isShowOffline; ConfigurationUtils.setShowOffline(isShowOffline); } /** * Returns <tt>true</tt> if offline contacts are shown, otherwise returns <tt>false</tt>. * * @return <tt>true</tt> if offline contacts are shown, otherwise returns <tt>false</tt> */ public boolean isShowOffline() { return isShowOffline; } /** * Returns <tt>true</tt> if offline contacts are shown or if the given <tt>MetaContact</tt> is * online, otherwise returns false. * * @param metaContact the <tt>MetaContact</tt> to check * @return <tt>true</tt> if the given <tt>MetaContact</tt> is matching this filter */ public boolean isMatching(MetaContact metaContact) { return isShowOffline || isContactOnline(metaContact); } /** * Returns <tt>true</tt> if offline contacts are shown or if the given <tt>MetaContact</tt> is * online, otherwise returns false. * * @param metaContact the <tt>MetaContact</tt> to check * @return <tt>true</tt> if the given <tt>MetaContact</tt> is matching this filter */ public boolean isMatching(SourceContact contact) { return isShowOffline || contact.getPresenceStatus().isOnline() || GuiActivator.getMUCService().isMUCSourceContact(contact); } /** * Returns <tt>true</tt> if offline contacts are shown or if the given <tt>MetaContactGroup</tt> * contains online contacts. * * @param metaGroup the <tt>MetaContactGroup</tt> to check * @return <tt>true</tt> if the given <tt>MetaContactGroup</tt> is matching this filter */ private boolean isMatching(MetaContactGroup metaGroup) { return isShowOffline || (metaGroup.countOnlineChildContacts() > 0) || MetaContactListSource.isNewGroup(metaGroup); } /** * Returns <tt>true</tt> if the given meta contact is online, <tt>false</tt> otherwise. * * @param contact the meta contact * @return <tt>true</tt> if the given meta contact is online, <tt>false</tt> otherwise */ private boolean isContactOnline(MetaContact contact) { // If for some reason the default contact is null we return false. Contact defaultContact = contact.getDefaultContact(); if (defaultContact == null) return false; // Lays on the fact that the default contact is the most connected. return defaultContact.getPresenceStatus().getStatus() >= PresenceStatus.ONLINE_THRESHOLD; } /** * Adds all contacts contained in the given <tt>MetaContactGroup</tt> matching the current filter * and not contained in the contact list. * * @param metaGroup the <tt>MetaContactGroup</tt>, which matching contacts to add * @param query the <tt>MetaContactQuery</tt> that notifies interested listeners of the results of * this matching * @param resultCount the initial result count we would insert directly to the contact list * without firing events */ private void addMatching(MetaContactGroup metaGroup, MetaContactQuery query, int resultCount) { Iterator<MetaContact> childContacts = metaGroup.getChildContacts(); while (childContacts.hasNext() && !query.isCanceled()) { MetaContact metaContact = childContacts.next(); if (isMatching(metaContact)) { resultCount++; if (resultCount <= INITIAL_CONTACT_COUNT) { UIGroup uiGroup = null; if (!MetaContactListSource.isRootGroup(metaGroup)) { synchronized (metaGroup) { uiGroup = MetaContactListSource.getUIGroup(metaGroup); if (uiGroup == null) uiGroup = MetaContactListSource.createUIGroup(metaGroup); } } if (logger.isDebugEnabled()) logger.debug("Presence filter contact added: " + metaContact.getDisplayName()); UIContactImpl newUIContact; synchronized (metaContact) { newUIContact = MetaContactListSource.getUIContact(metaContact); if (newUIContact == null) { newUIContact = MetaContactListSource.createUIContact(metaContact); } } GuiActivator.getContactList().addContact(newUIContact, uiGroup, true, true); query.setInitialResultCount(resultCount); } else { query.fireQueryEvent(metaContact); } } } // If in the meantime the filtering has been stopped we return here. if (query.isCanceled()) return; Iterator<MetaContactGroup> subgroups = metaGroup.getSubgroups(); while (subgroups.hasNext() && !query.isCanceled()) { MetaContactGroup subgroup = subgroups.next(); if (isMatching(subgroup)) { UIGroup uiGroup; synchronized (subgroup) { uiGroup = MetaContactListSource.getUIGroup(subgroup); if (uiGroup == null) uiGroup = MetaContactListSource.createUIGroup(subgroup); } GuiActivator.getContactList().addGroup(uiGroup, true); addMatching(subgroup, query, resultCount); } } } }
/** * Whiteboard session manager. * * @author Julien Waechter */ public class WhiteboardSessionManager implements WhiteboardObjectListener { private static final Logger logger = Logger.getLogger(WhiteboardSessionManager.class); /** A protocol provider map. */ private Map protocolProviderTable = new LinkedHashMap(); /** The default start WhiteboardSession. */ private WhiteboardSession wbTmpSession; /** List active WhitboarFrame started. */ private Vector wbFrames = new Vector(); /** List active WhitboarSession started. */ private Vector wbSessions; private OperationSetWhiteboarding opSetWb; public WhiteboardSessionManager() { if (WhiteboardActivator.getWhiteboardOperationSets() == null) return; Iterator opSets = WhiteboardActivator.getWhiteboardOperationSets().iterator(); while (opSets.hasNext()) { OperationSetWhiteboarding whiteboardOpSet = (OperationSetWhiteboarding) opSets.next(); whiteboardOpSet.addInvitationListener(new InvitationListener()); whiteboardOpSet.addPresenceListener(new PresenceListener()); } } /** * Initialize (a new) Whiteboard with contact * * @param contact Contact used to init whiteboard */ public void initWhiteboard(final Contact contact) { opSetWb = (OperationSetWhiteboarding) contact.getProtocolProvider().getOperationSet(OperationSetWhiteboarding.class); if (opSetWb == null) { logger.info("Contact does not support whiteboarding"); return; } WhiteboardFrame wbf = getWhiteboardFrame(contact); if (wbf != null) { wbf.setVisible(true); return; } new Thread() { public void run() { try { WhiteboardSession wbSession = opSetWb.createWhiteboardSession(contact.getDisplayName(), null); WhiteboardFrame wbFrame = new WhiteboardFrame(WhiteboardSessionManager.this, wbSession); wbFrames.add(wbFrame); wbFrame.setContact(contact); wbFrame.setVisible(true); wbSession.join(); wbSession.invite(contact.getAddress()); } catch (OperationFailedException e) { logger.error("Creating a whiteboard session failed.", e); } catch (OperationNotSupportedException e) { logger.error("Do not support create of whiteboard session", e); } } }.start(); } /** * Construct (with WhiteboardSession) and send a WhiteboardObject to a contact. * * @param wbSession the white-board session, to which the object would be send * @param ws WhiteboardShape to convert and send * @param c contact * @return WhiteboardObject sent */ public WhiteboardObject sendWhiteboardObject(WhiteboardSession wbSession, WhiteboardShape ws) throws OperationFailedException { Vector supportedWBO = new Vector(Arrays.asList(wbSession.getSupportedWhiteboardObjects())); if (ws instanceof WhiteboardObjectPath) { if (!supportedWBO.contains(WhiteboardObjectPath.NAME)) return null; WhiteboardObjectPath obj = (WhiteboardObjectPath) wbSession.createWhiteboardObject(WhiteboardObjectPath.NAME); ws.setID(obj.getID()); obj.setPoints(((WhiteboardObjectPath) ws).getPoints()); obj.setColor(ws.getColor()); obj.setThickness(ws.getThickness()); wbSession.sendWhiteboardObject(obj); return obj; } else if (ws instanceof WhiteboardObjectPolyLine) { if (!supportedWBO.contains(WhiteboardObjectPolyLine.NAME)) return null; WhiteboardObjectPolyLine obj = (WhiteboardObjectPolyLine) wbSession.createWhiteboardObject(WhiteboardObjectPolyLine.NAME); ws.setID(obj.getID()); obj.setPoints(((WhiteboardObjectPolyLine) ws).getPoints()); obj.setColor(ws.getColor()); obj.setThickness(ws.getThickness()); wbSession.sendWhiteboardObject(obj); return obj; } else if (ws instanceof WhiteboardObjectPolygon) { if (!supportedWBO.contains(WhiteboardObjectPolygon.NAME)) return null; WhiteboardObjectPolygon obj = (WhiteboardObjectPolygon) wbSession.createWhiteboardObject(WhiteboardObjectPolygon.NAME); ws.setID(obj.getID()); obj.setPoints(((WhiteboardObjectPolygon) ws).getPoints()); obj.setBackgroundColor(((WhiteboardObjectPolygon) ws).getBackgroundColor()); obj.setFill(((WhiteboardObjectPolygon) ws).isFill()); obj.setColor(ws.getColor()); obj.setThickness(ws.getThickness()); wbSession.sendWhiteboardObject(obj); return obj; } else if (ws instanceof WhiteboardObjectLine) { if (!supportedWBO.contains(WhiteboardObjectLine.NAME)) return null; WhiteboardObjectLine obj = (WhiteboardObjectLine) wbSession.createWhiteboardObject(WhiteboardObjectLine.NAME); ws.setID(obj.getID()); obj.setWhiteboardPointStart(((WhiteboardObjectLine) ws).getWhiteboardPointStart()); obj.setWhiteboardPointEnd(((WhiteboardObjectLine) ws).getWhiteboardPointEnd()); obj.setColor(ws.getColor()); obj.setThickness(ws.getThickness()); wbSession.sendWhiteboardObject(obj); return obj; } else if (ws instanceof WhiteboardObjectRect) { if (!supportedWBO.contains(WhiteboardObjectRect.NAME)) return null; WhiteboardObjectRect obj = (WhiteboardObjectRect) wbSession.createWhiteboardObject(WhiteboardObjectRect.NAME); ws.setID(obj.getID()); obj.setFill(((WhiteboardObjectRect) ws).isFill()); obj.setHeight(((WhiteboardObjectRect) ws).getHeight()); obj.setWhiteboardPoint(((WhiteboardObjectRect) ws).getWhiteboardPoint()); obj.setWidth((((WhiteboardObjectRect) ws)).getWidth()); obj.setColor(ws.getColor()); obj.setThickness(ws.getThickness()); wbSession.sendWhiteboardObject(obj); return obj; } else if (ws instanceof WhiteboardObjectCircle) { if (!supportedWBO.contains(WhiteboardObjectCircle.NAME)) return null; WhiteboardObjectCircle obj = (WhiteboardObjectCircle) wbSession.createWhiteboardObject(WhiteboardObjectCircle.NAME); ws.setID(obj.getID()); obj.setFill(((WhiteboardObjectCircle) ws).isFill()); obj.setRadius(((WhiteboardObjectCircle) ws).getRadius()); obj.setWhiteboardPoint(((WhiteboardObjectCircle) ws).getWhiteboardPoint()); obj.setBackgroundColor((((WhiteboardObjectCircle) ws)).getBackgroundColor()); obj.setColor(ws.getColor()); obj.setThickness(ws.getThickness()); wbSession.sendWhiteboardObject(obj); return obj; } else if (ws instanceof WhiteboardObjectText) { if (!supportedWBO.contains(WhiteboardObjectText.NAME)) return null; WhiteboardObjectText obj = (WhiteboardObjectText) wbSession.createWhiteboardObject(WhiteboardObjectText.NAME); ws.setID(obj.getID()); obj.setFontName(((WhiteboardObjectText) ws).getFontName()); obj.setFontSize(((WhiteboardObjectText) ws).getFontSize()); obj.setText(((WhiteboardObjectText) ws).getText()); obj.setWhiteboardPoint(((WhiteboardObjectText) ws).getWhiteboardPoint()); obj.setColor(ws.getColor()); obj.setThickness(ws.getThickness()); wbSession.sendWhiteboardObject(obj); return obj; } else if (ws instanceof WhiteboardObjectImage) { if (!supportedWBO.contains(WhiteboardObjectImage.NAME)) return null; WhiteboardObjectImage obj = (WhiteboardObjectImage) wbSession.createWhiteboardObject(WhiteboardObjectImage.NAME); ws.setID(obj.getID()); obj.setBackgroundImage(((WhiteboardObjectImage) ws).getBackgroundImage()); obj.setHeight(((WhiteboardObjectImage) ws).getHeight()); obj.setWhiteboardPoint(((WhiteboardObjectImage) ws).getWhiteboardPoint()); obj.setWidth(((WhiteboardObjectImage) ws).getWidth()); obj.setColor(ws.getColor()); obj.setThickness(ws.getThickness()); wbSession.sendWhiteboardObject(obj); return obj; } return null; } /** * Moves a <tt>WhiteboardShape</tt> from from one point to another on the board. * * @param wbSession the white-board session, to which the moved object belongs * @param ws the shape to move */ public void moveWhiteboardObject(WhiteboardSession wbSession, WhiteboardShape ws) { try { wbSession.moveWhiteboardObject(ws); } catch (OperationFailedException ex) { ex.printStackTrace(); } } /** * Deletes a <tt>WhiteboardShape</tt> from the white-board. * * @param wbSession the white-board session, to which the object belongs * @param ws the shape to delete */ public void deleteWhiteboardObject(WhiteboardSession wbSession, WhiteboardShape ws) { try { wbSession.deleteWhiteboardObject(ws); } catch (OperationFailedException ex) { ex.printStackTrace(); } } /** * Called when a modified <tt>WhiteboardObject</tt> has been received. * * @param evt the <tt>WhiteboardObjectReceivedEvent</tt> containing the modified whiteboardObject, * its sender and other details. */ public void whiteboardObjecModified(WhiteboardObjectModifiedEvent evt) { WhiteboardFrame wbf = getWhiteboardFrame(evt.getSourceWhiteboardSession()); if (wbf == null) return; wbf.setVisible(true); WhiteboardObject wbo = evt.getSourceWhiteboardObject(); wbf.receiveWhiteboardObject(wbo); } /** * Called when a new incoming <tt>WhiteboardObject</tt> has been received. * * @param evt the <tt>WhiteboardObjectReceivedEvent</tt> containing the newly received * WhiteboardObject, its sender and other details. */ public void whiteboardObjectReceived(WhiteboardObjectReceivedEvent evt) { /* * There are 2 cases when a message is received: * - an existing session * - or a new session */ WhiteboardFrame wbFrame = getWhiteboardFrame(evt.getSourceWhiteboardSession()); if (wbFrame == null) { logger.debug("New WBParticipant" + evt.getSourceContact().getDisplayName()); wbFrame = new WhiteboardFrame(this, evt.getSourceWhiteboardSession()); wbFrames.add(wbFrame); } wbFrame.setVisible(true); WhiteboardObject wbObject = evt.getSourceWhiteboardObject(); wbFrame.receiveWhiteboardObject(wbObject); } /** * Called when the underlying implementation has received an indication that a WhiteboardObject, * sent earlier has been successfully received by the destination. * * @param evt the WhiteboardObjectDeliveredEvent containing the id of the WhiteboardObject that * has caused the event. */ public void whiteboardObjectDelivered(WhiteboardObjectDeliveredEvent evt) { logger.debug( "WBObjectDeliveredEvent: The following object: " + evt.getSourceWhiteboardObject() + " has been delivered to " + evt.getDestinationContact().getDisplayName()); } /** * Called to indicate that delivery of a WhiteboardObject sent earlier has failed. Reason code and * phrase are contained by the <tt>WhiteboardObjectDeliveryFailedEvent</tt> * * @param evt the <tt>WhiteboardObjectDeliveryFailedEvent</tt> containing the ID of the * WhiteboardObject whose delivery has failed. */ public void whiteboardObjectDeliveryFailed(WhiteboardObjectDeliveryFailedEvent evt) { String errorMessage = null; if (evt.getErrorCode() == WhiteboardObjectDeliveryFailedEvent.NETWORK_FAILURE) { errorMessage = "Network failure."; } else if (evt.getErrorCode() == WhiteboardObjectDeliveryFailedEvent.OFFLINE_MESSAGES_NOT_SUPPORTED) { errorMessage = "Offline messages aren't supported."; } else if (evt.getErrorCode() == WhiteboardObjectDeliveryFailedEvent.PROVIDER_NOT_REGISTERED) { errorMessage = "Protocol provider is not registered."; } else if (evt.getErrorCode() == WhiteboardObjectDeliveryFailedEvent.INTERNAL_ERROR) { errorMessage = "An internal error occured."; } else if (evt.getErrorCode() == WhiteboardObjectDeliveryFailedEvent.UNKNOWN_ERROR) { errorMessage = "An unknown error occured."; } String debugErrorMessage = "WBObjectDeliveryFailedEvent: The following object: " + evt.getSourceWhiteboardObject() + " has NOT been delivered to " + evt.getDestinationContact().getDisplayName() + " because of the following error: " + errorMessage; logger.debug(debugErrorMessage); WhiteboardActivator.getUiService() .getPopupDialog() .showMessagePopupDialog(errorMessage, "Error", PopupDialog.ERROR_MESSAGE); } /** * Returns the WhiteboardFrame associated with the Contact. * * @param c contact * @return WhiteboardFrame with the Contact or null (if nothing found) */ private WhiteboardFrame getWhiteboardFrame(WhiteboardSession session) { WhiteboardFrame whiteboardFrame = null; for (int i = 0; i < wbFrames.size(); i++) { whiteboardFrame = (WhiteboardFrame) wbFrames.get(i); if (whiteboardFrame.getWhiteboardSession().equals(session)) return whiteboardFrame; } return null; } /** * Returns the WhiteboardFrame associated with the Contact. * * @param c contact * @return WhiteboardFrame with the Contact or null (if nothing found) */ private WhiteboardFrame getWhiteboardFrame(Contact contact) { WhiteboardFrame whiteboardFrame = null; for (int i = 0; i < wbFrames.size(); i++) { whiteboardFrame = (WhiteboardFrame) wbFrames.get(i); if (whiteboardFrame.getContact() != null && whiteboardFrame.getContact().equals(contact)) return whiteboardFrame; } return null; } /** * Called when a deleted <tt>WhiteboardObject</tt> has been received. * * @param evt the <tt>WhiteboardObjectDeletedEvent</tt> containing the identification of the * deleted WhiteboardObject, its sender and other details. */ public void whiteboardObjectDeleted(WhiteboardObjectDeletedEvent evt) { WhiteboardFrame wbf = getWhiteboardFrame(evt.getSourceWhiteboardSession()); if (wbf == null) { return; } wbf.setVisible(true); String id = evt.getId(); wbf.receiveDeleteWhiteboardObject(id); } /** * Listens for <tt>WhiteboardInvitationReceivedEvent</tt>s and shows a dialog to the user, where * she could accept, reject or ignore an incoming invitation. */ private class InvitationListener implements WhiteboardInvitationListener { public void invitationReceived(WhiteboardInvitationReceivedEvent evt) { OperationSetWhiteboarding whiteboardOpSet = evt.getSourceOperationSet(); InvitationReceivedDialog dialog = new InvitationReceivedDialog( WhiteboardSessionManager.this, whiteboardOpSet, evt.getInvitation()); dialog.pack(); dialog.setLocation( Toolkit.getDefaultToolkit().getScreenSize().width / 2 - dialog.getWidth() / 2, Toolkit.getDefaultToolkit().getScreenSize().height / 2 - dialog.getHeight() / 2); dialog.setVisible(true); } } /** * Called to accept an incoming invitation. Adds the invitation chat room to the list of chat * rooms and joins it. * * @param invitation the invitation to accept. */ public void acceptInvitation(WhiteboardInvitation invitation) { WhiteboardSession whiteboard = invitation.getTargetWhiteboard(); byte[] password = invitation.getWhiteboardPassword(); try { if (password == null) whiteboard.join(); else whiteboard.join(password); } catch (OperationFailedException e) { WhiteboardActivator.getUiService() .getPopupDialog() .showMessagePopupDialog( Resources.getString( "failedToJoinWhiteboard", new String[] {whiteboard.getWhiteboardID()}), Resources.getString("error"), PopupDialog.ERROR_MESSAGE); logger.error("Failed to join whiteboard: " + whiteboard.getWhiteboardID(), e); } } /** * Rejects the given invitation with the specified reason. * * @param whiteboardOpSet the operation set to use for rejecting the invitation * @param invitation the invitation to reject * @param reason the reason for the rejection */ public void rejectInvitation( OperationSetWhiteboarding whiteboardOpSet, WhiteboardInvitation invitation, String reason) { whiteboardOpSet.rejectInvitation(invitation, reason); } /** Listens for presence events. */ private class PresenceListener implements WhiteboardSessionPresenceListener { /** * Implements the <tt>WhiteboardSessionPresenceListener .whiteboardSessionPresenceChanged</tt> * method. */ public void whiteboardSessionPresenceChanged(WhiteboardSessionPresenceChangeEvent evt) { WhiteboardSession whiteboardSession = evt.getWhiteboardSession(); if (evt.getEventType().equals(WhiteboardSessionPresenceChangeEvent.LOCAL_USER_JOINED)) { whiteboardSession.addWhiteboardObjectListener(WhiteboardSessionManager.this); WhiteboardFrame frame = getWhiteboardFrame(evt.getWhiteboardSession()); if (frame == null) { frame = new WhiteboardFrame(WhiteboardSessionManager.this, whiteboardSession); frame.setVisible(true); wbFrames.add(frame); } } else if (evt.getEventType() .equals(WhiteboardSessionPresenceChangeEvent.LOCAL_USER_JOIN_FAILED)) { WhiteboardActivator.getUiService() .getPopupDialog() .showMessagePopupDialog( Resources.getString( "failedToJoinWhiteboard", new String[] {whiteboardSession.getWhiteboardID()}) + evt.getReason(), Resources.getString("error"), PopupDialog.ERROR_MESSAGE); } else if (evt.getEventType().equals(WhiteboardSessionPresenceChangeEvent.LOCAL_USER_LEFT)) { WhiteboardFrame frame = getWhiteboardFrame(whiteboardSession); if (frame == null) return; wbFrames.remove(frame); frame.dispose(); whiteboardSession.removeWhiteboardObjectListener(WhiteboardSessionManager.this); } else if (evt.getEventType() .equals(WhiteboardSessionPresenceChangeEvent.LOCAL_USER_KICKED)) { } else if (evt.getEventType() .equals(WhiteboardSessionPresenceChangeEvent.LOCAL_USER_DROPPED)) { } } } /** * Removes a white board frame. * * @param frame the frame to remove */ public void removeWhiteboardWindow(WhiteboardFrame frame) { synchronized (wbFrames) { wbFrames.remove(frame); } } }
/** * The contactlist panel not only contains the contact list but it has the role of a message * dispatcher. It process all sent and received messages as well as all typing notifications. Here * are managed all contact list mouse events. * * @author Yana Stamcheva * @author Hristo Terezov */ public class ContactListPane extends SIPCommScrollPane implements MessageListener, TypingNotificationsListener, FileTransferListener, ContactListListener, PluginComponentListener { /** Serial version UID. */ private static final long serialVersionUID = 0L; private final MainFrame mainFrame; private TreeContactList contactList; private final TypingTimer typingTimer = new TypingTimer(); private CommonRightButtonMenu commonRightButtonMenu; private final Logger logger = Logger.getLogger(ContactListPane.class); private final ChatWindowManager chatWindowManager; /** * Creates the contactlist scroll panel defining the parent frame. * * @param mainFrame The parent frame. */ public ContactListPane(MainFrame mainFrame) { this.mainFrame = mainFrame; this.chatWindowManager = GuiActivator.getUIService().getChatWindowManager(); this.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); this.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, Color.GRAY)); this.initPluginComponents(); } /** * Initializes the contact list. * * @param contactListService The MetaContactListService which will be used for a contact list data * model. */ public void initList(MetaContactListService contactListService) { this.contactList = new TreeContactList(mainFrame); // We should first set the contact list to the GuiActivator, so that // anybody could get it from there. GuiActivator.setContactList(contactList); // By default we set the current filter to be the presence filter. contactList.applyFilter(TreeContactList.presenceFilter); TransparentPanel transparentPanel = new TransparentPanel(new BorderLayout()); transparentPanel.add(contactList, BorderLayout.NORTH); this.setViewportView(transparentPanel); transparentPanel.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1)); this.contactList.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); this.contactList.addContactListListener(this); this.addMouseListener( new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { if ((e.getModifiers() & InputEvent.BUTTON3_MASK) != 0) { commonRightButtonMenu = new CommonRightButtonMenu(mainFrame); commonRightButtonMenu.setInvoker(ContactListPane.this); commonRightButtonMenu.setLocation( e.getX() + mainFrame.getX() + 5, e.getY() + mainFrame.getY() + 105); commonRightButtonMenu.setVisible(true); } } }); } /** * Returns the contact list. * * @return the contact list */ public TreeContactList getContactList() { return this.contactList; } /** * Implements the ContactListListener.contactSelected method. * * @param evt the <tt>ContactListEvent</tt> that notified us */ public void contactClicked(ContactListEvent evt) { // We're interested only in two click events. if (evt.getClickCount() < 2) return; UIContact descriptor = evt.getSourceContact(); // We're currently only interested in MetaContacts. if (descriptor.getDescriptor() instanceof MetaContact) { MetaContact metaContact = (MetaContact) descriptor.getDescriptor(); // Searching for the right proto contact to use as default for the // chat conversation. Contact defaultContact = metaContact.getDefaultContact(OperationSetBasicInstantMessaging.class); // do nothing if (defaultContact == null) { defaultContact = metaContact.getDefaultContact(OperationSetSmsMessaging.class); if (defaultContact == null) return; } ProtocolProviderService defaultProvider = defaultContact.getProtocolProvider(); OperationSetBasicInstantMessaging defaultIM = defaultProvider.getOperationSet(OperationSetBasicInstantMessaging.class); ProtocolProviderService protoContactProvider; OperationSetBasicInstantMessaging protoContactIM; boolean isOfflineMessagingSupported = defaultIM != null && !defaultIM.isOfflineMessagingSupported(); if (defaultContact.getPresenceStatus().getStatus() < 1 && (!isOfflineMessagingSupported || !defaultProvider.isRegistered())) { Iterator<Contact> protoContacts = metaContact.getContacts(); while (protoContacts.hasNext()) { Contact contact = protoContacts.next(); protoContactProvider = contact.getProtocolProvider(); protoContactIM = protoContactProvider.getOperationSet(OperationSetBasicInstantMessaging.class); if (protoContactIM != null && protoContactIM.isOfflineMessagingSupported() && protoContactProvider.isRegistered()) { defaultContact = contact; } } } ContactEventHandler contactHandler = mainFrame.getContactHandler(defaultContact.getProtocolProvider()); contactHandler.contactClicked(defaultContact, evt.getClickCount()); } else if (descriptor.getDescriptor() instanceof SourceContact) { SourceContact contact = (SourceContact) descriptor.getDescriptor(); List<ContactDetail> imDetails = contact.getContactDetails(OperationSetBasicInstantMessaging.class); List<ContactDetail> mucDetails = contact.getContactDetails(OperationSetMultiUserChat.class); if (imDetails != null && imDetails.size() > 0) { ProtocolProviderService pps = imDetails.get(0).getPreferredProtocolProvider(OperationSetBasicInstantMessaging.class); GuiActivator.getUIService() .getChatWindowManager() .startChat(contact.getContactAddress(), pps); } else if (mucDetails != null && mucDetails.size() > 0) { ChatRoomWrapper room = GuiActivator.getMUCService().findChatRoomWrapperFromSourceContact(contact); if (room == null) { // lets check by id ProtocolProviderService pps = mucDetails.get(0).getPreferredProtocolProvider(OperationSetMultiUserChat.class); room = GuiActivator.getMUCService() .findChatRoomWrapperFromChatRoomID(contact.getContactAddress(), pps); if (room == null) { GuiActivator.getMUCService() .createChatRoom( contact.getContactAddress(), pps, new ArrayList<String>(), "", false, false, false); } } if (room != null) GuiActivator.getMUCService().openChatRoom(room); } else { List<ContactDetail> smsDetails = contact.getContactDetails(OperationSetSmsMessaging.class); if (smsDetails != null && smsDetails.size() > 0) { GuiActivator.getUIService() .getChatWindowManager() .startChat(contact.getContactAddress(), true); } } } } /** * Implements the ContactListListener.groupSelected method. * * @param evt the <tt>ContactListEvent</tt> that notified us */ public void groupClicked(ContactListEvent evt) {} /** We're not interested in group selection events here. */ public void groupSelected(ContactListEvent evt) {} /** We're not interested in contact selection events here. */ public void contactSelected(ContactListEvent evt) {} /** * When a message is received determines whether to open a new chat window or chat window tab, or * to indicate that a message is received from a contact which already has an open chat. When the * chat is found checks if in mode "Auto popup enabled" and if this is the case shows the message * in the appropriate chat panel. * * @param evt the event containing details on the received message */ public void messageReceived(MessageReceivedEvent evt) { if (logger.isTraceEnabled()) logger.trace("MESSAGE RECEIVED from contact: " + evt.getSourceContact().getAddress()); Contact protocolContact = evt.getSourceContact(); ContactResource contactResource = evt.getContactResource(); Message message = evt.getSourceMessage(); int eventType = evt.getEventType(); MetaContact metaContact = GuiActivator.getContactListService().findMetaContactByContact(protocolContact); if (metaContact != null) { messageReceived( protocolContact, contactResource, metaContact, message, eventType, evt.getTimestamp(), evt.getCorrectedMessageUID(), evt.isPrivateMessaging(), evt.getPrivateMessagingContactRoom()); } else { if (logger.isTraceEnabled()) logger.trace("MetaContact not found for protocol contact: " + protocolContact + "."); } } /** * When a message is received determines whether to open a new chat window or chat window tab, or * to indicate that a message is received from a contact which already has an open chat. When the * chat is found checks if in mode "Auto popup enabled" and if this is the case shows the message * in the appropriate chat panel. * * @param protocolContact the source contact of the event * @param contactResource the resource from which the contact is writing * @param metaContact the metacontact containing <tt>protocolContact</tt> * @param message the message to deliver * @param eventType the event type * @param timestamp the timestamp of the event * @param correctedMessageUID the identifier of the corrected message * @param isPrivateMessaging if <tt>true</tt> the message is received from private messaging * contact. * @param privateContactRoom the chat room associated with the private messaging contact. */ private void messageReceived( final Contact protocolContact, final ContactResource contactResource, final MetaContact metaContact, final Message message, final int eventType, final Date timestamp, final String correctedMessageUID, final boolean isPrivateMessaging, final ChatRoom privateContactRoom) { if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater( new Runnable() { public void run() { messageReceived( protocolContact, contactResource, metaContact, message, eventType, timestamp, correctedMessageUID, isPrivateMessaging, privateContactRoom); } }); return; } // Obtain the corresponding chat panel. final ChatPanel chatPanel = chatWindowManager.getContactChat( metaContact, protocolContact, contactResource, message.getMessageUID()); // Show an envelope on the sender contact in the contact list and // in the systray. if (!chatPanel.isChatFocused()) contactList.setActiveContact(metaContact, true); // Distinguish the message type, depending on the type of event that // we have received. String messageType = null; if (eventType == MessageReceivedEvent.CONVERSATION_MESSAGE_RECEIVED) { messageType = Chat.INCOMING_MESSAGE; } else if (eventType == MessageReceivedEvent.SYSTEM_MESSAGE_RECEIVED) { messageType = Chat.SYSTEM_MESSAGE; } else if (eventType == MessageReceivedEvent.SMS_MESSAGE_RECEIVED) { messageType = Chat.SMS_MESSAGE; } String contactAddress = (contactResource != null) ? protocolContact.getAddress() + " (" + contactResource.getResourceName() + ")" : protocolContact.getAddress(); chatPanel.addMessage( contactAddress, protocolContact.getDisplayName(), timestamp, messageType, message.getContent(), message.getContentType(), message.getMessageUID(), correctedMessageUID); String resourceName = (contactResource != null) ? contactResource.getResourceName() : null; if (isPrivateMessaging) { chatWindowManager.openPrivateChatForChatRoomMember(privateContactRoom, protocolContact); } else { chatWindowManager.openChat(chatPanel, false); } ChatTransport chatTransport = chatPanel.getChatSession().findChatTransportForDescriptor(protocolContact, resourceName); chatPanel.setSelectedChatTransport(chatTransport, true); } /** * When a sent message is delivered shows it in the chat conversation panel. * * @param evt the event containing details on the message delivery */ public void messageDelivered(MessageDeliveredEvent evt) { Contact contact = evt.getDestinationContact(); MetaContact metaContact = GuiActivator.getContactListService().findMetaContactByContact(contact); if (logger.isTraceEnabled()) logger.trace("MESSAGE DELIVERED to contact: " + contact.getAddress()); ChatPanel chatPanel = chatWindowManager.getContactChat(metaContact, false); if (chatPanel != null) { Message msg = evt.getSourceMessage(); ProtocolProviderService protocolProvider = contact.getProtocolProvider(); if (logger.isTraceEnabled()) logger.trace( "MESSAGE DELIVERED: process message to chat for contact: " + contact.getAddress() + " MESSAGE: " + msg.getContent()); chatPanel.addMessage( this.mainFrame.getAccountAddress(protocolProvider), this.mainFrame.getAccountDisplayName(protocolProvider), evt.getTimestamp(), Chat.OUTGOING_MESSAGE, msg.getContent(), msg.getContentType(), msg.getMessageUID(), evt.getCorrectedMessageUID()); if (evt.isSmsMessage() && !ConfigurationUtils.isSmsNotifyTextDisabled()) { chatPanel.addMessage( contact.getDisplayName(), new Date(), Chat.ACTION_MESSAGE, GuiActivator.getResources().getI18NString("service.gui.SMS_SUCCESSFULLY_SENT"), "text"); } } } /** * Shows a warning message to the user when message delivery has failed. * * @param evt the event containing details on the message delivery failure */ public void messageDeliveryFailed(MessageDeliveryFailedEvent evt) { logger.error(evt.getReason()); String errorMsg = null; Message sourceMessage = (Message) evt.getSource(); Contact sourceContact = evt.getDestinationContact(); MetaContact metaContact = GuiActivator.getContactListService().findMetaContactByContact(sourceContact); if (evt.getErrorCode() == MessageDeliveryFailedEvent.OFFLINE_MESSAGES_NOT_SUPPORTED) { errorMsg = GuiActivator.getResources() .getI18NString( "service.gui.MSG_DELIVERY_NOT_SUPPORTED", new String[] {sourceContact.getDisplayName()}); } else if (evt.getErrorCode() == MessageDeliveryFailedEvent.NETWORK_FAILURE) { errorMsg = GuiActivator.getResources().getI18NString("service.gui.MSG_NOT_DELIVERED"); } else if (evt.getErrorCode() == MessageDeliveryFailedEvent.PROVIDER_NOT_REGISTERED) { errorMsg = GuiActivator.getResources().getI18NString("service.gui.MSG_SEND_CONNECTION_PROBLEM"); } else if (evt.getErrorCode() == MessageDeliveryFailedEvent.INTERNAL_ERROR) { errorMsg = GuiActivator.getResources().getI18NString("service.gui.MSG_DELIVERY_INTERNAL_ERROR"); } else { errorMsg = GuiActivator.getResources().getI18NString("service.gui.MSG_DELIVERY_ERROR"); } String reason = evt.getReason(); if (reason != null) errorMsg += " " + GuiActivator.getResources() .getI18NString("service.gui.ERROR_WAS", new String[] {reason}); ChatPanel chatPanel = chatWindowManager.getContactChat(metaContact, sourceContact); chatPanel.addMessage( sourceContact.getAddress(), metaContact.getDisplayName(), new Date(), Chat.OUTGOING_MESSAGE, sourceMessage.getContent(), sourceMessage.getContentType(), sourceMessage.getMessageUID(), evt.getCorrectedMessageUID()); chatPanel.addErrorMessage(metaContact.getDisplayName(), errorMsg); chatWindowManager.openChat(chatPanel, false); } /** * Informs the user what is the typing state of his chat contacts. * * @param evt the event containing details on the typing notification */ public void typingNotificationReceived(TypingNotificationEvent evt) { if (typingTimer.isRunning()) typingTimer.stop(); String notificationMsg = ""; MetaContact metaContact = GuiActivator.getContactListService().findMetaContactByContact(evt.getSourceContact()); String contactName = metaContact.getDisplayName() + " "; if (contactName.equals("")) { contactName = GuiActivator.getResources().getI18NString("service.gui.UNKNOWN") + " "; } int typingState = evt.getTypingState(); ChatPanel chatPanel = chatWindowManager.getContactChat(metaContact, false); if (typingState == OperationSetTypingNotifications.STATE_TYPING) { notificationMsg = GuiActivator.getResources() .getI18NString("service.gui.CONTACT_TYPING", new String[] {contactName}); // Proactive typing notification if (!chatWindowManager.isChatOpenedFor(metaContact)) { return; } if (chatPanel != null) chatPanel.addTypingNotification(notificationMsg); typingTimer.setMetaContact(metaContact); typingTimer.start(); } else if (typingState == OperationSetTypingNotifications.STATE_PAUSED) { notificationMsg = GuiActivator.getResources() .getI18NString("service.gui.CONTACT_PAUSED_TYPING", new String[] {contactName}); if (chatPanel != null) chatPanel.addTypingNotification(notificationMsg); typingTimer.setMetaContact(metaContact); typingTimer.start(); } else { if (chatPanel != null) chatPanel.removeTypingNotification(); } } /** * Called to indicate that sending typing notification has failed. * * @param evt a <tt>TypingNotificationEvent</tt> containing the sender of the notification and its * type. */ public void typingNotificationDeliveryFailed(TypingNotificationEvent evt) { if (typingTimer.isRunning()) typingTimer.stop(); String notificationMsg = ""; MetaContact metaContact = GuiActivator.getContactListService().findMetaContactByContact(evt.getSourceContact()); String contactName = metaContact.getDisplayName(); if (contactName.equals("")) { contactName = GuiActivator.getResources().getI18NString("service.gui.UNKNOWN") + " "; } ChatPanel chatPanel = chatWindowManager.getContactChat(metaContact, false); notificationMsg = GuiActivator.getResources() .getI18NString("service.gui.CONTACT_TYPING_SEND_FAILED", new String[] {contactName}); // Proactive typing notification if (!chatWindowManager.isChatOpenedFor(metaContact)) { return; } if (chatPanel != null) chatPanel.addErrorSendingTypingNotification(notificationMsg); typingTimer.setMetaContact(metaContact); typingTimer.start(); } /** * When a request has been received we show it to the user through the chat session renderer. * * @param event <tt>FileTransferRequestEvent</tt> * @see FileTransferListener#fileTransferRequestReceived(FileTransferRequestEvent) */ public void fileTransferRequestReceived(FileTransferRequestEvent event) { IncomingFileTransferRequest request = event.getRequest(); Contact sourceContact = request.getSender(); MetaContact metaContact = GuiActivator.getContactListService().findMetaContactByContact(sourceContact); final ChatPanel chatPanel = chatWindowManager.getContactChat(metaContact, sourceContact); chatPanel.addIncomingFileTransferRequest( event.getFileTransferOperationSet(), request, event.getTimestamp()); ChatTransport chatTransport = chatPanel.getChatSession().findChatTransportForDescriptor(sourceContact, null); chatPanel.setSelectedChatTransport(chatTransport, true); // Opens the chat panel with the new message in the UI thread. chatWindowManager.openChat(chatPanel, false); } /** * Nothing to do here, because we already know when a file transfer is created. * * @param event the <tt>FileTransferCreatedEvent</tt> that notified us */ public void fileTransferCreated(FileTransferCreatedEvent event) {} /** * Called when a new <tt>IncomingFileTransferRequest</tt> has been rejected. Nothing to do here, * because we are the one who rejects the request. * * @param event the <tt>FileTransferRequestEvent</tt> containing the received request which was * rejected. */ public void fileTransferRequestRejected(FileTransferRequestEvent event) {} /** * Called when an <tt>IncomingFileTransferRequest</tt> has been canceled from the contact who sent * it. * * @param event the <tt>FileTransferRequestEvent</tt> containing the request which was canceled. */ public void fileTransferRequestCanceled(FileTransferRequestEvent event) {} /** * Returns the right button menu of the contact list. * * @return the right button menu of the contact list */ public CommonRightButtonMenu getCommonRightButtonMenu() { return commonRightButtonMenu; } /** * The TypingTimer is started after a PAUSED typing notification is received. It waits 5 seconds * and if no other typing event occurs removes the PAUSED message from the chat status panel. */ private class TypingTimer extends Timer { /** Serial version UID. */ private static final long serialVersionUID = 0L; private MetaContact metaContact; public TypingTimer() { // Set delay super(5 * 1000, null); this.addActionListener(new TimerActionListener()); } private class TimerActionListener implements ActionListener { public void actionPerformed(ActionEvent e) { ChatPanel chatPanel = chatWindowManager.getContactChat(metaContact, false); if (chatPanel != null) chatPanel.removeTypingNotification(); } } private void setMetaContact(MetaContact metaContact) { this.metaContact = metaContact; } } private void initPluginComponents() { // Search for plugin components registered through the OSGI bundle // context. ServiceReference[] serRefs = null; String osgiFilter = "(" + Container.CONTAINER_ID + "=" + Container.CONTAINER_CONTACT_LIST.getID() + ")"; try { serRefs = GuiActivator.bundleContext.getServiceReferences( PluginComponentFactory.class.getName(), osgiFilter); } catch (InvalidSyntaxException exc) { logger.error("Could not obtain plugin reference.", exc); } if (serRefs != null) { for (ServiceReference serRef : serRefs) { PluginComponentFactory factory = (PluginComponentFactory) GuiActivator.bundleContext.getService(serRef); PluginComponent component = factory.getPluginComponentInstance(this); Object selectedValue = getContactList().getSelectedValue(); if (selectedValue instanceof MetaContact) { component.setCurrentContact((MetaContact) selectedValue); } else if (selectedValue instanceof MetaContactGroup) { component.setCurrentContactGroup((MetaContactGroup) selectedValue); } String pluginConstraints = factory.getConstraints(); Object constraints; if (pluginConstraints != null) constraints = UIServiceImpl.getBorderLayoutConstraintsFromContainer(pluginConstraints); else constraints = BorderLayout.SOUTH; this.add((Component) component.getComponent(), constraints); this.repaint(); } } GuiActivator.getUIService().addPluginComponentListener(this); } /** * Adds the plugin component given by <tt>event</tt> to this panel if it's its container. * * @param event the <tt>PluginComponentEvent</tt> that notified us */ public void pluginComponentAdded(PluginComponentEvent event) { PluginComponentFactory factory = event.getPluginComponentFactory(); // If the container id doesn't correspond to the id of the plugin // container we're not interested. if (!factory.getContainer().equals(Container.CONTAINER_CONTACT_LIST)) return; Object constraints = UIServiceImpl.getBorderLayoutConstraintsFromContainer(factory.getConstraints()); if (constraints == null) constraints = BorderLayout.SOUTH; PluginComponent pluginComponent = factory.getPluginComponentInstance(this); this.add((Component) pluginComponent.getComponent(), constraints); Object selectedValue = getContactList().getSelectedValue(); if (selectedValue instanceof MetaContact) { pluginComponent.setCurrentContact((MetaContact) selectedValue); } else if (selectedValue instanceof MetaContactGroup) { pluginComponent.setCurrentContactGroup((MetaContactGroup) selectedValue); } this.revalidate(); this.repaint(); } /** * Removes the plugin component given by <tt>event</tt> if previously added in this panel. * * @param event the <tt>PluginComponentEvent</tt> that notified us */ public void pluginComponentRemoved(PluginComponentEvent event) { PluginComponentFactory factory = event.getPluginComponentFactory(); // If the container id doesn't correspond to the id of the plugin // container we're not interested. if (!factory.getContainer().equals(Container.CONTAINER_CONTACT_LIST)) return; this.remove((Component) factory.getPluginComponentInstance(this).getComponent()); } }
/** * A {@link AbstractPluginComponent} that registers the Off-the-Record button in the main chat * toolbar. * * @author George Politis * @author Marin Dzhigarov */ public class OtrMetaContactButton extends AbstractPluginComponent implements ScOtrEngineListener, ScOtrKeyManagerListener { /** The logger */ private final Logger logger = Logger.getLogger(OtrMetaContactButton.class); private SIPCommButton button; private OtrContact otrContact; private AnimatedImage animatedPadlockImage; private Image finishedPadlockImage; private Image verifiedLockedPadlockImage; private Image unverifiedLockedPadlockImage; private Image unlockedPadlockImage; private Image timedoutPadlockImage; public void sessionStatusChanged(OtrContact otrContact) { // OtrMetaContactButton.this.contact can be null. if (otrContact.equals(OtrMetaContactButton.this.otrContact)) { setStatus(OtrActivator.scOtrEngine.getSessionStatus(otrContact)); } } public void contactPolicyChanged(Contact contact) { // OtrMetaContactButton.this.contact can be null. if (OtrMetaContactButton.this.otrContact != null && contact.equals(OtrMetaContactButton.this.otrContact.contact)) { setPolicy(OtrActivator.scOtrEngine.getContactPolicy(contact)); } } public void globalPolicyChanged() { if (OtrMetaContactButton.this.otrContact != null) setPolicy(OtrActivator.scOtrEngine.getContactPolicy(otrContact.contact)); } public void contactVerificationStatusChanged(OtrContact otrContact) { // OtrMetaContactButton.this.contact can be null. if (otrContact.equals(OtrMetaContactButton.this.otrContact)) { setStatus(OtrActivator.scOtrEngine.getSessionStatus(otrContact)); } } public OtrMetaContactButton(Container container, PluginComponentFactory parentFactory) { super(container, parentFactory); /* * XXX This OtrMetaContactButton instance cannot be added as a listener * to scOtrEngine and scOtrKeyManager without being removed later on * because the latter live forever. Unfortunately, the dispose() method * of this instance is never executed. OtrWeakListener will keep this * instance as a listener of scOtrEngine and scOtrKeyManager for as long * as this instance is necessary. And this instance will be strongly * referenced by the JMenuItems which depict it. So when the JMenuItems * are gone, this instance will become obsolete and OtrWeakListener will * remove it as a listener of scOtrEngine and scOtrKeyManager. */ new OtrWeakListener<OtrMetaContactButton>( this, OtrActivator.scOtrEngine, OtrActivator.scOtrKeyManager); } /** * Gets the <code>SIPCommButton</code> which is the component of this plugin. If the button * doesn't exist, it's created. * * @return the <code>SIPCommButton</code> which is the component of this plugin */ @SuppressWarnings("fallthrough") private SIPCommButton getButton() { if (button == null) { button = new SIPCommButton(null, null); button.setEnabled(false); button.setPreferredSize(new Dimension(25, 25)); button.setToolTipText( OtrActivator.resourceService.getI18NString("plugin.otr.menu.OTR_TOOLTIP")); Image i1 = null, i2 = null, i3 = null; try { i1 = ImageIO.read( OtrActivator.resourceService.getImageURL("plugin.otr.LOADING_ICON1_22x22")); i2 = ImageIO.read( OtrActivator.resourceService.getImageURL("plugin.otr.LOADING_ICON2_22x22")); i3 = ImageIO.read( OtrActivator.resourceService.getImageURL("plugin.otr.LOADING_ICON3_22x22")); finishedPadlockImage = ImageIO.read( OtrActivator.resourceService.getImageURL("plugin.otr.FINISHED_ICON_22x22")); verifiedLockedPadlockImage = ImageIO.read( OtrActivator.resourceService.getImageURL("plugin.otr.ENCRYPTED_ICON_22x22")); unverifiedLockedPadlockImage = ImageIO.read( OtrActivator.resourceService.getImageURL( "plugin.otr.ENCRYPTED_UNVERIFIED_ICON_22x22")); unlockedPadlockImage = ImageIO.read( OtrActivator.resourceService.getImageURL("plugin.otr.PLAINTEXT_ICON_22x22")); timedoutPadlockImage = ImageIO.read(OtrActivator.resourceService.getImageURL("plugin.otr.BROKEN_ICON_22x22")); } catch (IOException e) { logger.debug("Failed to load padlock image"); } animatedPadlockImage = new AnimatedImage(button, i1, i2, i3); button.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { if (otrContact == null) return; switch (OtrActivator.scOtrEngine.getSessionStatus(otrContact)) { case ENCRYPTED: OtrPolicy policy = OtrActivator.scOtrEngine.getContactPolicy(otrContact.contact); policy.setSendWhitespaceTag(false); OtrActivator.scOtrEngine.setContactPolicy(otrContact.contact, policy); case FINISHED: case LOADING: // Default action for finished, encrypted and loading // sessions is end session. OtrActivator.scOtrEngine.endSession(otrContact); break; case TIMED_OUT: case PLAINTEXT: policy = OtrActivator.scOtrEngine.getContactPolicy(otrContact.contact); OtrPolicy globalPolicy = OtrActivator.scOtrEngine.getGlobalPolicy(); policy.setSendWhitespaceTag(globalPolicy.getSendWhitespaceTag()); OtrActivator.scOtrEngine.setContactPolicy(otrContact.contact, policy); // Default action for timed_out and plaintext sessions // is start session. OtrActivator.scOtrEngine.startSession(otrContact); break; } } }); } return button; } /* * Implements PluginComponent#getComponent(). Returns the SIPCommButton * which is the component of this plugin creating it first if it doesn't * exist. */ public Object getComponent() { return getButton(); } /* * Implements PluginComponent#getName(). */ public String getName() { return ""; } /* * Implements PluginComponent#setCurrentContact(Contact). */ @Override public void setCurrentContact(Contact contact) { setCurrentContact(contact, null); } public void setCurrentContact(Contact contact, String resourceName) { if (contact == null) { this.otrContact = null; this.setPolicy(null); this.setStatus(ScSessionStatus.PLAINTEXT); return; } if (resourceName == null) { OtrContact otrContact = OtrContactManager.getOtrContact(contact, null); if (this.otrContact == otrContact) return; this.otrContact = otrContact; this.setStatus(OtrActivator.scOtrEngine.getSessionStatus(otrContact)); this.setPolicy(OtrActivator.scOtrEngine.getContactPolicy(contact)); return; } for (ContactResource resource : contact.getResources()) { if (resource.getResourceName().equals(resourceName)) { OtrContact otrContact = OtrContactManager.getOtrContact(contact, resource); if (this.otrContact == otrContact) return; this.otrContact = otrContact; this.setStatus(OtrActivator.scOtrEngine.getSessionStatus(otrContact)); this.setPolicy(OtrActivator.scOtrEngine.getContactPolicy(contact)); return; } } logger.debug("Could not find resource for contact " + contact); } /* * Implements PluginComponent#setCurrentContact(MetaContact). */ @Override public void setCurrentContact(MetaContact metaContact) { setCurrentContact((metaContact == null) ? null : metaContact.getDefaultContact()); } /** * Sets the button enabled status according to the passed in {@link OtrPolicy}. * * @param contactPolicy the {@link OtrPolicy}. */ private void setPolicy(OtrPolicy contactPolicy) { getButton().setEnabled(contactPolicy != null && contactPolicy.getEnableManual()); } /** * Sets the button icon according to the passed in {@link SessionStatus}. * * @param status the {@link SessionStatus}. */ private void setStatus(ScSessionStatus status) { animatedPadlockImage.pause(); Image image; String tipKey; switch (status) { case ENCRYPTED: PublicKey pubKey = OtrActivator.scOtrEngine.getRemotePublicKey(otrContact); String fingerprint = OtrActivator.scOtrKeyManager.getFingerprintFromPublicKey(pubKey); image = OtrActivator.scOtrKeyManager.isVerified(otrContact.contact, fingerprint) ? verifiedLockedPadlockImage : unverifiedLockedPadlockImage; tipKey = OtrActivator.scOtrKeyManager.isVerified(otrContact.contact, fingerprint) ? "plugin.otr.menu.VERIFIED" : "plugin.otr.menu.UNVERIFIED"; break; case FINISHED: image = finishedPadlockImage; tipKey = "plugin.otr.menu.FINISHED"; break; case PLAINTEXT: image = unlockedPadlockImage; tipKey = "plugin.otr.menu.START_OTR"; break; case LOADING: image = animatedPadlockImage; animatedPadlockImage.start(); tipKey = "plugin.otr.menu.LOADING_OTR"; break; case TIMED_OUT: image = timedoutPadlockImage; tipKey = "plugin.otr.menu.TIMED_OUT"; break; default: return; } SIPCommButton button = getButton(); button.setIconImage(image); button.setToolTipText(OtrActivator.resourceService.getI18NString(tipKey)); button.repaint(); } @Override public void multipleInstancesDetected(OtrContact contact) {} @Override public void outgoingSessionChanged(OtrContact otrContact) { // OtrMetaContactButton.this.contact can be null. if (otrContact.equals(OtrMetaContactButton.this.otrContact)) { setStatus(OtrActivator.scOtrEngine.getSessionStatus(otrContact)); } } }