/** * 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; } } }
/** * 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(); } }