diff options
19 files changed, 2508 insertions, 3 deletions
@@ -560,6 +560,7 @@ <target name="bundles" depends="bundle-util,bundle-configuration,bundle-configuration-slick, bundle-history,bundle-history-slick,bundle-messagehistory, bundle-msghistory-slick, + bundle-callhistory, bundle-callhistory-slick, bundle-netaddr,bundle-netaddr-slick,bundle-slickless, bundle-slick-runner,bundle-sip,bundle-sip-slick,bundle-fileaccess, bundle-fileaccess-slick,bundle-media,bundle-media-slick, @@ -610,6 +611,27 @@ </jar> </target> + <!--BUNDLE-CALLHISTORY--> + <target name="bundle-callhistory"> + <jar compress="false" destfile="${bundles.dest}/callhistory.jar" + manifest="src/net/java/sip/communicator/impl/callhistory/callhistory.manifest.mf"> + + <zipfileset dir="${dest}/net/java/sip/communicator/service/callhistory" + prefix="net/java/sip/communicator/service/callhistory"/> + <zipfileset dir="${dest}/net/java/sip/communicator/impl/callhistory" + prefix="net/java/sip/communicator/impl/callhistory" /> + </jar> + </target> + + <!--BUNDLE-CALLEHISTORY-SLICK--> + <target name="bundle-callhistory-slick"> + <jar compress="false" destfile="${bundles.dest}/callhistory-slick.jar" + manifest="test/net/java/sip/communicator/slick/callhistory/callhistory.slick.manifest.mf"> + <zipfileset dir="${dest}/net/java/sip/communicator/slick/callhistory" + prefix="net/java/sip/communicator/slick/callhistory"/> + </jar> + </target> + <!--BUNDLE-FILEACCESS--> <target name="bundle-fileaccess"> <jar compress="false" destfile="${bundles.dest}/fileaccess.jar" diff --git a/lib/oscar.client.run.properties b/lib/oscar.client.run.properties index e5239d2..80901f9 100644 --- a/lib/oscar.client.run.properties +++ b/lib/oscar.client.run.properties @@ -54,7 +54,8 @@ oscar.auto.start.3= \ oscar.auto.start.4= \ file:sc-bundles/history.jar \ - file:sc-bundles/msghistory.jar + file:sc-bundles/msghistory.jar \ + file:sc-bundles/callhistory.jar oscar.auto.start.66= \ diff --git a/lib/oscar.unit.test.properties b/lib/oscar.unit.test.properties index b13d50f..4190a4a 100644 --- a/lib/oscar.unit.test.properties +++ b/lib/oscar.unit.test.properties @@ -50,7 +50,8 @@ oscar.auto.start.4= \ file:sc-bundles/protocol-sip.jar \ file:sc-bundles/media.jar \ file:sc-bundles/meta-cl.jar \ - file:sc-bundles/msghistory.jar + file:sc-bundles/msghistory.jar \ + file:sc-bundles/callhistory.jar oscar.auto.start.5= \ file:sc-bundles/slickless.jar \ @@ -65,7 +66,8 @@ oscar.auto.start.5= \ file:sc-bundles/protocol-icq-slick.jar \ file:sc-bundles/protocol-sip-slick.jar \ file:sc-bundles/protocol-jabber-slick.jar \ - file:sc-bundles/msghistory-slick.jar + file:sc-bundles/msghistory-slick.jar \ + file:sc-bundles/callhistory-slick.jar oscar.auto.start.100= \ file:sc-bundles/slick-runner.jar diff --git a/lib/testing.properties b/lib/testing.properties index 9bcb44c..2ea5153 100644 --- a/lib/testing.properties +++ b/lib/testing.properties @@ -12,6 +12,7 @@ test.list=ConfigurationServiceLick \ IcqProtocolProviderSlick \ SipProtocolProviderServiceLick \ MsgHistoryServiceLick \ + CallHistoryServiceLick \ JabberProtocolProviderSlick # Set the name of the meta contact list file to use during testing so that diff --git a/src/net/java/sip/communicator/impl/callhistory/CallHistoryActivator.java b/src/net/java/sip/communicator/impl/callhistory/CallHistoryActivator.java new file mode 100755 index 0000000..2ea38b0 --- /dev/null +++ b/src/net/java/sip/communicator/impl/callhistory/CallHistoryActivator.java @@ -0,0 +1,69 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.callhistory; + +import org.osgi.framework.*; +import net.java.sip.communicator.service.history.*; +import net.java.sip.communicator.util.*; +import net.java.sip.communicator.service.callhistory.*; + +/** + * Activates the CallHistoryService + * + * @author Damian Minkov + */ +public class CallHistoryActivator + implements BundleActivator +{ + private static Logger logger = + Logger.getLogger(CallHistoryActivator.class); + + private CallHistoryServiceImpl callHistoryService = null; + + /** + * Initialize and start call history + * + * @param bundleContext BundleContext + * @throws Exception + */ + public void start(BundleContext bundleContext) throws Exception + { + try{ + + logger.logEntry(); + + ServiceReference refHistory = bundleContext.getServiceReference( + HistoryService.class.getName()); + + HistoryService historyService = (HistoryService) + bundleContext.getService(refHistory); + + //Create and start the call history service. + callHistoryService = + new CallHistoryServiceImpl(); + // set the configuration and history service + callHistoryService.setHistoryService(historyService); + + callHistoryService.start(bundleContext); + + bundleContext.registerService( + CallHistoryService.class.getName(), callHistoryService, null); + + logger.info("Call History Service ...[REGISTERED]"); + } + finally + { + logger.logExit(); + } + + } + + public void stop(BundleContext bundleContext) throws Exception + { + + } +} diff --git a/src/net/java/sip/communicator/impl/callhistory/CallHistoryServiceImpl.java b/src/net/java/sip/communicator/impl/callhistory/CallHistoryServiceImpl.java new file mode 100644 index 0000000..c360d4a --- /dev/null +++ b/src/net/java/sip/communicator/impl/callhistory/CallHistoryServiceImpl.java @@ -0,0 +1,900 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.callhistory; + +import java.io.*; +import java.util.*; + +import org.osgi.framework.*; +import net.java.sip.communicator.service.callhistory.*; +import net.java.sip.communicator.service.callhistory.event.*; +import net.java.sip.communicator.service.contactlist.*; +import net.java.sip.communicator.service.history.*; +import net.java.sip.communicator.service.history.event.*; +import net.java.sip.communicator.service.history.event.ProgressEvent; +import net.java.sip.communicator.service.history.records.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.util.*; + +/** + * The Call History Service stores info about the calls made. + * Logs calls info for all protocol providers that support basic telephony + * (i.e. those that implement OperationSetBasicTelephony). + * + * @author Damian Minkov + */ +public class CallHistoryServiceImpl + implements CallHistoryService, + CallListener, + ServiceListener +{ + /** + * The logger for this class. + */ + private static Logger logger = Logger + .getLogger(CallHistoryServiceImpl.class); + + private static String[] STRUCTURE_NAMES = + new String[] { "callStart", "callEnd", "dir", "callParticipantIDs", + "callParticipantStart", "callParticipantEnd" }; + + private static HistoryRecordStructure recordStructure = + new HistoryRecordStructure(STRUCTURE_NAMES); + + private static final String DELIM = ","; + + /** + * The BundleContext that we got from the OSGI bus. + */ + private BundleContext bundleContext = null; + + private HistoryService historyService = null; + + private Object syncRoot_HistoryService = new Object(); + + private Hashtable progressListeners = new Hashtable(); + + private Vector currentCallRecords = new Vector(); + + private HistoryCallChangeListener historyCallChangeListener + = new HistoryCallChangeListener(); + + public HistoryService getHistoryService() + { + return historyService; + } + + /** + * Returns all the calls made by all the contacts + * in the supplied metacontact after the given date + * + * @param contact MetaContact + * @param startDate Date the start date of the calls + * @return Collection of CallRecords with CallParticipantRecord + * @throws RuntimeException + */ + public Collection findByStartDate(MetaContact contact, Date startDate) + throws RuntimeException + { + throw new UnsupportedOperationException("Not implemented yet!"); + } + + /** + * Returns all the calls made after the given date + * + * @param startDate Date the start date of the calls + * @return Collection of CallRecords with CallParticipantRecord + * @throws RuntimeException + */ + public Collection findByStartDate(Date startDate) throws RuntimeException + { + TreeSet result = new TreeSet(new CallRecordComparator()); + try + { + // the default ones + History history = this.getHistory(null, null); + HistoryReader reader = history.getReader(); + addHistorySearchProgressListeners(reader, 1); + QueryResultSet rs = reader.findByStartDate(startDate); + while (rs.hasNext()) + { + HistoryRecord hr = (HistoryRecord) rs.next(); + result.add(convertHistoryRecordToCallRecord(hr)); + } + removeHistorySearchProgressListeners(reader); + } + catch (IOException ex) + { + logger.error("Could not read history", ex); + } + + return result; + } + + /** + * Returns all the calls made by all the contacts + * in the supplied metacontact before the given date + * + * @param contact MetaContact + * @param endDate Date the end date of the calls + * @return Collection of CallRecords with CallParticipantRecord + * @throws RuntimeException + */ + public Collection findByEndDate(MetaContact contact, Date endDate) + throws RuntimeException + { + throw new UnsupportedOperationException("Not implemented yet!"); + } + + /** + * Returns all the calls made before the given date + * + * @param endDate Date the end date of the calls + * @return Collection of CallRecords with CallParticipantRecord + * @throws RuntimeException + */ + public Collection findByEndDate(Date endDate) throws RuntimeException + { + TreeSet result = new TreeSet(new CallRecordComparator()); + try + { + // the default ones + History history = this.getHistory(null, null); + HistoryReader reader = history.getReader(); + addHistorySearchProgressListeners(reader, 1); + QueryResultSet rs = reader.findByEndDate(endDate); + while (rs.hasNext()) + { + HistoryRecord hr = (HistoryRecord) rs.next(); + result.add(convertHistoryRecordToCallRecord(hr)); + } + removeHistorySearchProgressListeners(reader); + } + catch (IOException ex) + { + logger.error("Could not read history", ex); + } + + return result; + } + + /** + * Returns all the calls made by all the contacts + * in the supplied metacontact between the given dates + * + * @param contact MetaContact + * @param startDate Date the start date of the calls + * @param endDate Date the end date of the conversations + * @return Collection of CallRecords with CallParticipantRecord + * @throws RuntimeException + */ + public Collection findByPeriod(MetaContact contact, Date startDate, Date endDate) + throws RuntimeException + { + throw new UnsupportedOperationException("Not implemented yet!"); + } + + /** + * Returns all the calls made between the given dates + * + * @param startDate Date the start date of the calls + * @param endDate Date the end date of the conversations + * @return Collection of CallRecords with CallParticipantRecord + * @throws RuntimeException + */ + public Collection findByPeriod(Date startDate, Date endDate) throws + RuntimeException + { + TreeSet result = new TreeSet(new CallRecordComparator()); + try + { + // the default ones + History history = this.getHistory(null, null); + HistoryReader reader = history.getReader(); + addHistorySearchProgressListeners(reader, 1); + QueryResultSet rs = reader.findByPeriod(startDate, endDate); + while (rs.hasNext()) + { + HistoryRecord hr = (HistoryRecord) rs.next(); + result.add(convertHistoryRecordToCallRecord(hr)); + } + removeHistorySearchProgressListeners(reader); + } + catch (IOException ex) + { + logger.error("Could not read history", ex); + } + + return result; + } + + /** + * Returns the supplied number of calls by all the contacts + * in the supplied metacontact + * + * @param contact MetaContact + * @param count calls count + * @return Collection of CallRecords with CallParticipantRecord + * @throws RuntimeException + */ + public Collection findLast(MetaContact contact, int count) + throws RuntimeException + { + throw new UnsupportedOperationException("Not implemented yet!"); + } + + /** + * Returns the supplied number of calls made + * + * @param count calls count + * @return Collection of CallRecords with CallParticipantRecord + * @throws RuntimeException + */ + public Collection findLast(int count) throws RuntimeException + { + TreeSet result = new TreeSet(new CallRecordComparator()); + try + { + // the default ones + History history = this.getHistory(null, null); + QueryResultSet rs = history.getReader().findLast(count); + while (rs.hasNext()) + { + HistoryRecord hr = (HistoryRecord) rs.next(); + result.add(convertHistoryRecordToCallRecord(hr)); + } + } + catch (IOException ex) + { + logger.error("Could not read history", ex); + } + + return result; + } + + /** + * Returns the history by specified local and remote contact + * if one of them is null the default is used + * + * @param localContact Contact + * @param remoteContact Contact + * @return History + * @throws IOException + */ + private History getHistory(Contact localContact, Contact remoteContact) + throws IOException { + History retVal = null; + + String localId = localContact == null ? "default" : localContact + .getAddress(); + String remoteId = remoteContact == null ? "default" : remoteContact + .getAddress(); + + HistoryID historyId = HistoryID.createFromID(new String[] { "callhistory", + localId, remoteId }); + + if (this.historyService.isHistoryExisting(historyId)) + { + retVal = this.historyService.getHistory(historyId); + } else { + retVal = this.historyService.createHistory(historyId, + recordStructure); + } + + return retVal; + } + + /** + * Used to convert HistoryRecord in CallReord and CallParticipantRecord + * which are returned by the finder methods + * + * @param hr HistoryRecord + * @return Object CallRecord + */ + private Object convertHistoryRecordToCallRecord(HistoryRecord hr) + { + CallRecord result = new CallRecord(); + + LinkedList callParticipantIDs = null; + LinkedList callParticipantStart = null; + LinkedList callParticipantEnd = null; + + // History structure + // 0 - callStart + // 1 - callEnd + // 2 - dir + // 3 - callParticipantIDs + // 4 - callParticipantStart + // 5 - callParticipantEnd + + for (int i = 0; i < hr.getPropertyNames().length; i++) + { + String propName = hr.getPropertyNames()[i]; + String value = hr.getPropertyValues()[i]; + + if(propName.equals(STRUCTURE_NAMES[0])) + result.setStartTime(new Date(Long.parseLong(value))); + else if(propName.equals(STRUCTURE_NAMES[1])) + result.setEndTime(new Date(Long.parseLong(value))); + else if(propName.equals(STRUCTURE_NAMES[2])) + result.setDirection(value); + else if(propName.equals(STRUCTURE_NAMES[3])) + callParticipantIDs = getCSVs(value); + else if(propName.equals(STRUCTURE_NAMES[4])) + callParticipantStart = getCSVs(value); + else if(propName.equals(STRUCTURE_NAMES[5])) + callParticipantEnd = getCSVs(value); + } + + for (int i = 0; i < callParticipantIDs.size(); i++) + { + + CallParticipantRecord cpr = new CallParticipantRecord( + (String)callParticipantIDs.get(i), + new Date(Long.parseLong((String)callParticipantStart.get(i))), + new Date(Long.parseLong((String)callParticipantEnd.get(i))) + ); + result.getParticipantRecords().add(cpr); + } + + return result; + } + + /** + * Returns list of String items contained in the supplied string + * separated by DELIM + * @param str String + * @return LinkedList + */ + private LinkedList getCSVs(String str) + { + LinkedList result = new LinkedList(); + StringTokenizer toks = new StringTokenizer(str, DELIM); + while(toks.hasMoreTokens()) + { + result.add(toks.nextToken()); + } + return result; + } + + /** + * starts the service. Check the current registerd protocol providers + * which supports BasicTelephony and adds calls listener to them + * + * @param bc BundleContext + */ + public void start(BundleContext bc) + { + logger.debug("Starting the call history implementation."); + this.bundleContext = bc; + + // start listening for newly register or removed protocol providers + bc.addServiceListener(this); + + ServiceReference[] protocolProviderRefs = null; + try + { + protocolProviderRefs = bc.getServiceReferences( + ProtocolProviderService.class.getName(), + null); + } + catch (InvalidSyntaxException ex) + { + // this shouldn't happen since we're providing no parameter string + // but let's log just in case. + logger.error( + "Error while retrieving service refs", ex); + return; + } + + // in case we found any + if (protocolProviderRefs != null) + { + logger.debug("Found " + + protocolProviderRefs.length + + " already installed providers."); + for (int i = 0; i < protocolProviderRefs.length; i++) + { + ProtocolProviderService provider = (ProtocolProviderService) bc + .getService(protocolProviderRefs[i]); + + this.handleProviderAdded(provider); + } + } + } + + /** + * Writes the given record to the history service + * @param callRecord CallRecord + * @param source Contact + * @param destination Contact + */ + private void writeCall(CallRecord callRecord, Contact source, + Contact destination) + { + try { + History history = this.getHistory(source, destination); + HistoryWriter historyWriter = history.getWriter(); + + StringBuffer callParticipantIDs = new StringBuffer(); + StringBuffer callParticipantStartTime = new StringBuffer(); + StringBuffer callParticipantEndTime = new StringBuffer(); + + Iterator iter = callRecord.getParticipantRecords().iterator(); + while (iter.hasNext()) + { + if(callParticipantIDs.length() > 0) + { + callParticipantIDs.append(DELIM); + callParticipantStartTime.append(DELIM); + callParticipantEndTime.append(DELIM); + } + + CallParticipantRecord item = (CallParticipantRecord) iter.next(); + callParticipantIDs.append(item.getParticipantAddress()); + callParticipantStartTime.append(String.valueOf(item.getStartTime().getTime())); + callParticipantEndTime.append(String.valueOf(item.getEndTime().getTime())); + } + + historyWriter.addRecord(new String[] { + String.valueOf(callRecord.getStartTime().getTime()), + String.valueOf(callRecord.getEndTime().getTime()), + callRecord.getDirection(), + callParticipantIDs.toString(), + callParticipantStartTime.toString(), + callParticipantEndTime.toString()}, + new Date()); // this date is when the history record is written + } catch (IOException e) + { + logger.error("Could not add call to history", e); + } + } + + /** + * Set the configuration service. + * + * @param historyService HistoryService + * @throws IOException + * @throws IllegalArgumentException + */ + public void setHistoryService(HistoryService historyService) + throws IllegalArgumentException, IOException { + synchronized (this.syncRoot_HistoryService) + { + this.historyService = historyService; + + logger.debug("New history service registered."); + } + } + + /** + * Remove a configuration service. + * + * @param historyService HistoryService + */ + public void unsetHistoryService(HistoryService historyService) + { + synchronized (this.syncRoot_HistoryService) + { + if (this.historyService == historyService) + { + this.historyService = null; + + logger.debug("History service unregistered."); + } + } + } + + /** + * When new protocol provider is registered we check + * does it supports BasicTelephony and if so add a listener to it + * + * @param serviceEvent ServiceEvent + */ + public void serviceChanged(ServiceEvent serviceEvent) + { + Object sService = bundleContext.getService(serviceEvent.getServiceReference()); + + logger.trace("Received a service event for: " + sService.getClass().getName()); + + // we don't care if the source service is not a protocol provider + if (! (sService instanceof ProtocolProviderService)) + { + return; + } + + logger.debug("Service is a protocol provider."); + if (serviceEvent.getType() == ServiceEvent.REGISTERED) + { + logger.debug("Handling registration of a new Protocol Provider."); + + this.handleProviderAdded((ProtocolProviderService)sService); + } + else if (serviceEvent.getType() == ServiceEvent.UNREGISTERING) + { + this.handleProviderRemoved( (ProtocolProviderService) sService); + } + + } + + /** + * Used to attach the Call History Service to existing or + * just registered protocol provider. Checks if the provider has implementation + * of OperationSetBasicTelephony + * + * @param provider ProtocolProviderService + */ + private void handleProviderAdded(ProtocolProviderService provider) + { + logger.debug("Adding protocol provider " + provider.getProtocolName()); + + // check whether the provider has a basic telephony operation set + OperationSetBasicTelephony opSetTelephony + = (OperationSetBasicTelephony) provider + .getSupportedOperationSets().get( + OperationSetBasicTelephony.class.getName()); + + if (opSetTelephony != null) + { + opSetTelephony.addCallListener(this); + } + else + { + logger.trace("Service did not have a basic telephony op. set."); + } + } + + /** + * Removes the specified provider from the list of currently known providers + * and ignores all the calls made by it + * + * @param provider the ProtocolProviderService that has been unregistered. + */ + private void handleProviderRemoved(ProtocolProviderService provider) + { + OperationSetBasicTelephony opSetTelephony + = (OperationSetBasicTelephony) provider + .getSupportedOperationSets().get( + OperationSetBasicTelephony.class.getName()); + + if (opSetTelephony != null) + { + opSetTelephony.removeCallListener(this); + } + } + + /** + * Adding progress listener for monitoring progress of search process + * + * @param listener HistorySearchProgressListener + */ + public void addSearchProgressListener(CallHistorySearchProgressListener + listener) + { + synchronized(progressListeners){ + HistorySearchProgressListener wrapperListener = + new SearchProgressWrapper(listener); + progressListeners.put(listener, wrapperListener); + } + } + + /** + * Removing progress listener + * + * @param listener HistorySearchProgressListener + */ + public void removeSearchProgressListener( + CallHistorySearchProgressListener listener) + { + synchronized(progressListeners){ + progressListeners.remove(listener); + } + } + + /** + * Add the registered CallHistorySearchProgressListeners + * to the given HistoryReader + * + * @param reader HistoryReader + * @param countContacts number of contacts will search + */ + private void addHistorySearchProgressListeners( + HistoryReader reader, int countContacts) + { + synchronized(progressListeners) + { + Iterator iter = progressListeners.values().iterator(); + while (iter.hasNext()) + { + SearchProgressWrapper l = + (SearchProgressWrapper) iter.next(); + l.contactCount = countContacts; + reader.addSearchProgressListener(l); + } + } + } + + /** + * Removes the registered CallHistorySearchProgressListeners + * from the given HistoryReader + * + * @param reader HistoryReader + */ + private void removeHistorySearchProgressListeners(HistoryReader reader) + { + synchronized(progressListeners) + { + Iterator iter = progressListeners.values().iterator(); + while (iter.hasNext()) + { + SearchProgressWrapper l = + (SearchProgressWrapper) iter.next(); + l.clear(); + reader.removeSearchProgressListener(l); + } + } + } + + /** + * Gets all the history readers for the contacts in the given MetaContact + * @param contact MetaContact + * @return Hashtable + */ + private Hashtable getHistoryReaders(MetaContact contact) + { + Hashtable readers = new Hashtable(); + Iterator iter = contact.getContacts(); + while (iter.hasNext()) + { + Contact item = (Contact) iter.next(); + + try + { + History history = this.getHistory(null, item); + readers.put(item, history.getReader()); + } + catch (IOException e) + { + logger.error("Could not read history", e); + } + } + return readers; + } + + /** + * CallListener implementation for incoming calls + * @param event CallEvent + */ + public void incomingCallReceived(CallEvent event) + { + handleNewCall(event.getSourceCall(), CallRecord.IN); + } + + /** + * CallListener implementation for outgoing calls + * @param event CallEvent + */ + public void outgoingCallCreated(CallEvent event) + { + logger.info("outgoingCallCreated"); + handleNewCall(event.getSourceCall(), CallRecord.OUT); + } + + /** + * CallListener implementation for call endings + * @param event CallEvent + */ + public void callEnded(CallEvent event) + { + logger.info("callEnded"); + Date endTime = new Date(); + + CallRecord callRecord = findCallRecord(event.getSourceCall()); + + // no such call + if (callRecord == null) + return; + + callRecord.setEndTime(endTime); + + writeCall(callRecord, null, null); + + currentCallRecords.remove(callRecord); + } + + /** + * Adding a record for joining participant + * @param callParticipant CallParticipant + */ + private void handleParticipantAdded(CallParticipant callParticipant) + { + CallRecord callRecord = findCallRecord(callParticipant.getCall()); + + // no such call + if(callRecord == null) + return; + + CallParticipantRecord newRec = new CallParticipantRecord( + callParticipant.getAddress(), + new Date(), + null); + + callRecord.getParticipantRecords().add(newRec); + } + + /** + * Adding a record for removing participant from call + * @param callParticipant CallParticipant + */ + private void handleParticipantRemoved(CallParticipant callParticipant) + { + logger.info("handleParticipantRemoved"); + CallRecord callRecord = findCallRecord(callParticipant.getCall()); + String pAddress = callParticipant.getAddress(); + + CallParticipantRecord cpRecord = + callRecord.findParticipantRecord(pAddress); + + // no such participant + if(cpRecord == null) + return; + + cpRecord.setEndTime(new Date()); + } + + /** + * Finding a CallRecord for the given call + * @param call Call + * @return CallRecord + */ + private CallRecord findCallRecord(Call call) + { + Iterator iter = currentCallRecords.iterator(); + while (iter.hasNext()) + { + CallRecord item = (CallRecord) iter.next(); + if(item.getSourceCall().equals(call)) + return item; + } + + return null; + } + + /** + * Adding a record for a new call + * @param sourceCall Call + * @param direction String + */ + private void handleNewCall(Call sourceCall, String direction) + { + // if call exist. its not new + if(currentCallRecords.contains(sourceCall)) + return; + + CallRecord newRecord = new CallRecord( + sourceCall, + direction, + new Date(), + null); + + sourceCall.addCallChangeListener(historyCallChangeListener); + + currentCallRecords.add(newRecord); + + // if has already perticipants Dispatch them + Iterator iter = sourceCall.getCallParticipants(); + while (iter.hasNext()) + { + CallParticipant item = (CallParticipant) iter.next(); + handleParticipantAdded(item); + } + } + + /** + * A wrapper around HistorySearchProgressListener + * that fires events for CallHistorySearchProgressListener + */ + private class SearchProgressWrapper + implements HistorySearchProgressListener + { + private CallHistorySearchProgressListener listener = null; + int contactCount = 0; + int currentContactCount = 0; + int currentProgress = 0; + int lastHistoryProgress = 0; + + SearchProgressWrapper(CallHistorySearchProgressListener listener) + { + this.listener = listener; + } + + public void progressChanged(ProgressEvent evt) + { + int progress = getProgressMapping(evt.getProgress()); + + listener.progressChanged( + new net.java.sip.communicator.service.callhistory.event. + ProgressEvent(CallHistoryServiceImpl.this, evt, progress)); + } + + /** + * Calculates the progress according the count of the contacts + * we will search + * @param historyProgress int + * @return int + */ + private int getProgressMapping(int historyProgress) + { + currentProgress += (historyProgress - lastHistoryProgress)/contactCount; + + if(historyProgress == HistorySearchProgressListener.PROGRESS_MAXIMUM_VALUE) + { + currentContactCount++; + lastHistoryProgress = 0; + + // this is the last one and the last event fire the max + // there will be looses in currentProgress due to the devision + if(currentContactCount == contactCount) + currentProgress = + CallHistorySearchProgressListener.PROGRESS_MAXIMUM_VALUE; + } + else + lastHistoryProgress = historyProgress; + + return currentProgress; + } + + /** + * clear the values + */ + void clear() + { + contactCount = 0; + currentProgress = 0; + lastHistoryProgress = 0; + currentContactCount = 0; + } + } + + + + /** + * Used to compare CallRecords + * and to be ordered in TreeSet according their timestamp + */ + private class CallRecordComparator + implements Comparator + { + public int compare(Object o1, Object o2) + { + return ((CallRecord)o1).getStartTime(). + compareTo(((CallRecord)o2).getStartTime()); + } + } + + /** + * Receive events for adding or removing participants from a call + */ + private class HistoryCallChangeListener + implements CallChangeListener + { + public void callParticipantAdded(CallParticipantEvent evt) + { + handleParticipantAdded(evt.getSourceCallParticipant()); + } + + public void callParticipantRemoved(CallParticipantEvent evt) + { + handleParticipantRemoved(evt.getSourceCallParticipant()); + } + + public void callStateChanged(CallChangeEvent evt) + { + } + } +} diff --git a/src/net/java/sip/communicator/impl/callhistory/callhistory.manifest.mf b/src/net/java/sip/communicator/impl/callhistory/callhistory.manifest.mf new file mode 100644 index 0000000..e50b60e --- /dev/null +++ b/src/net/java/sip/communicator/impl/callhistory/callhistory.manifest.mf @@ -0,0 +1,19 @@ +Bundle-Activator: net.java.sip.communicator.impl.callhistory.CallHistoryActivator +Bundle-Name: Call History Service Provider +Bundle-Description: A bundle that implements the call history package. +Bundle-Vendor: sip-communicator.org +Bundle-Version: 0.0.1 +Import-Package: org.ungoverned.gravity.servicebinder, + org.osgi.framework, + net.java.sip.communicator.service.fileaccess, + net.java.sip.communicator.service.history, + net.java.sip.communicator.service.history.event, + net.java.sip.communicator.service.contactlist, + net.java.sip.communicator.service.history.records, + net.java.sip.communicator.util, + net.java.sip.communicator.service.protocol, + net.java.sip.communicator.service.protocol.icqconstants, + net.java.sip.communicator.service.protocol.event, +Export-Package: net.java.sip.communicator.service.callhistory, + net.java.sip.communicator.service.callhistory.event +Metadata-Location: /net/java/sip/communicator/impl/msghistory/callhistory.metadata.xml diff --git a/src/net/java/sip/communicator/impl/callhistory/callhistory.metadata.xml b/src/net/java/sip/communicator/impl/callhistory/callhistory.metadata.xml new file mode 100755 index 0000000..75a2e9c --- /dev/null +++ b/src/net/java/sip/communicator/impl/callhistory/callhistory.metadata.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<bundle> + <component class="net.java.sip.communicator.impl.msghistory.MessageHistoryServiceImpl"> + <provides service="net.java.sip.communicator.service.msghistory.MessageHistoryService"/> + + <requires service="net.java.sip.communicator.service.configuration.ConfigurationService" + filter="" + policy="static" + cardinality="1..1" + bind-method="setConfigurationService" + unbind-method="unsetConfigurationService" /> + + <requires service="net.java.sip.communicator.service.history.HistoryService" + filter="" + policy="static" + cardinality="1..1" + bind-method="setHistoryService" + unbind-method="unsetHistoryService" /> + + <requires service="net.java.sip.communicator.service.protocol.ProtocolProviderService" + filter="" + cardinality="1..n" + policy="dynamic" + bind-method="addProtocolProvider" + unbind-method="removeProtocolProvider" /> + + </component> +</bundle> diff --git a/src/net/java/sip/communicator/impl/protocol/mock/MockCall.java b/src/net/java/sip/communicator/impl/protocol/mock/MockCall.java new file mode 100644 index 0000000..c87e0b1 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/mock/MockCall.java @@ -0,0 +1,161 @@ +package net.java.sip.communicator.impl.protocol.mock; + +import java.util.*; + +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.impl.protocol.sip.CallParticipantSipImpl; +import net.java.sip.communicator.util.Logger; + +/** + * @author Damian Minkov + */ +public class MockCall + extends Call + implements CallParticipantListener +{ + private static final Logger logger = Logger.getLogger(MockCall.class); + + /** + * A list containing all <tt>CallParticipant</tt>s of this call. + */ + private Vector callParticipants = new Vector(); + + /** + * The state that this call is currently in. + */ + private CallState callState = CallState.CALL_INITIALIZATION; + + + public MockCall(MockProvider sourceProvider) + { + super(sourceProvider); + } + + /** + * Returns an iterator over all call participants. + * + * @return an Iterator over all participants currently involved in the + * call. + */ + public Iterator getCallParticipants() + { + return callParticipants.iterator(); + } + + /** + * Returns the number of participants currently associated with this call. + * + * @return an <tt>int</tt> indicating the number of participants + * currently associated with this call. + */ + public int getCallParticipantsCount() + { + return callParticipants.size(); + } + + /** + * Returns the state that this call is currently in. + * + * @return a reference to the <tt>CallState</tt> instance that the call + * is currently in. + */ + public CallState getCallState() + { + return callState; + } + + /** + * Adds <tt>callParticipant</tt> to the list of participants in this call. + * If the call participant is already included in the call, the method has + * no effect. + * + * @param callParticipant the new <tt>CallParticipant</tt> + */ + public void addCallParticipant(MockCallParticipant callParticipant) + { + if(callParticipants.contains(callParticipant)) + return; + + callParticipant.addCallParticipantListener(this); + + this.callParticipants.add(callParticipant); + + logger.info("Will fire participant added"); + + fireCallParticipantEvent( + callParticipant, CallParticipantEvent.CALL_PARTICIPANT_ADDED); + } + + /** + * Removes <tt>callParticipant</tt> from the list of participants in this + * call. The method has no effect if there was no such participant in the + * call. + * + * @param callParticipant the <tt>CallParticipant</tt> leaving the call; + */ + public void removeCallParticipant(MockCallParticipant callParticipant) + { + if(!callParticipants.contains(callParticipant)) + return; + + this.callParticipants.remove(callParticipant); + callParticipant.removeCallParticipantListener(this); + + fireCallParticipantEvent( + callParticipant, CallParticipantEvent.CALL_PARTICIPANT_REMVOVED); + + if(callParticipants.size() == 0) + setCallState(CallState.CALL_ENDED); + } + + /** + * Sets the state of this call and fires a call change event notifying + * registered listenres for the change. + * + * @param newState a reference to the <tt>CallState</tt> instance that + * the call is to enter. + */ + public void setCallState(CallState newState) + { + CallState oldState = getCallState(); + + if(oldState == newState) + return; + + this.callState = newState; + + fireCallChangeEvent( + CallChangeEvent.CALL_STATE_CHANGE, oldState, newState); + } + + public void participantStateChanged(CallParticipantChangeEvent evt) + { + if ( ( (CallParticipantState) evt.getNewValue()) + == CallParticipantState.DISCONNECTED + || ( (CallParticipantState) evt.getNewValue()) + == CallParticipantState.FAILED) + { + removeCallParticipant( + (MockCallParticipant) evt.getSourceCallParticipant()); + } + else if ( ( (CallParticipantState) evt.getNewValue()) + == CallParticipantState.CONNECTED + && getCallState().equals(CallState.CALL_INITIALIZATION)) + { + setCallState(CallState.CALL_IN_PROGRESS); + } + } + + public void participantDisplayNameChanged(CallParticipantChangeEvent evt) + { + } + + public void participantAddressChanged(CallParticipantChangeEvent evt) + { + } + + public void participantImageChanged(CallParticipantChangeEvent evt) + { + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/mock/MockCallParticipant.java b/src/net/java/sip/communicator/impl/protocol/mock/MockCallParticipant.java new file mode 100644 index 0000000..ac1e57e --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/mock/MockCallParticipant.java @@ -0,0 +1,160 @@ +package net.java.sip.communicator.impl.protocol.mock; + +import java.util.*; + +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.CallParticipantChangeEvent; + +/** + * <p> </p> + * + * <p> </p> + * + * <p> </p> + * + * <p> </p> + * + * @author Damian Minkov + */ +public class MockCallParticipant + extends AbstractCallParticipant +{ + /** + * The sip address of this participant + */ + private String participantAddress = null; + + /** + * The call participant belongs to. + */ + private MockCall call; + + /** + * A string uniquely identifying the participant. + */ + private String participantID; + + /** + * Indicates the date when is call participant passed into its current state. + */ + protected Date currentStateStartDate = new Date(); + + /** + * The state of the call participant. + */ + protected CallParticipantState callParticipantState = + CallParticipantState.UNKNOWN; + + + public MockCallParticipant(String address, MockCall owningCall) + { + this.participantAddress = address; + this.call = owningCall; + + call.addCallParticipant(this); + + //create the uid + this.participantID = String.valueOf( System.currentTimeMillis()) + + String.valueOf(hashCode()); + } + + /** + * Returns a String locator for that participant. + * + * @return the participant's address or phone number. + */ + public String getAddress() + { + return participantAddress; + } + + /** + * Returns a reference to the call that this participant belongs to. + * + * @return a reference to the call containing this participant. + */ + public Call getCall() + { + return call; + } + + /** + * Returns the date (time) when this call participant acquired its + * current status. + * + * @return a java.util.Date object containing the date when this call + * participant entered its current state. + */ + public Date getCurrentStateStartDate() + { + return currentStateStartDate; + } + + /** + * Returns a human readable name representing this participant. + * + * @return a String containing a name for that participant. + */ + public String getDisplayName() + { + return participantAddress; + } + + /** + * The method returns an image representation of the call participant + * (e.g. + * + * @return byte[] a byte array containing the image or null if no image + * is available. + */ + public byte[] getImage() + { + return null; + } + + /** + * Returns a unique identifier representing this participant. + * + * @return an identifier representing this call participant. + */ + public String getParticipantID() + { + return participantID; + } + + /** + * Returns an object representing the current state of that participant. + * + * @return a CallParticipantState instance representin the participant's + * state. + */ + public CallParticipantState getState() + { + return callParticipantState; + } + + /** + * Causes this CallParticipant to enter the specified state. The method also + * sets the currentStateStartDate field and fires a + * CallParticipantChangeEvent. + * + * @param newState the state this call participant should enter. + * @param reason a string that could be set to contain a human readable + * explanation for the transition (particularly handy when moving into a + * FAILED state). + */ + protected void setState(CallParticipantState newState, String reason) + { + CallParticipantState oldState = getState(); + + if(oldState == newState) + return; + + this.callParticipantState = newState; + this.currentStateStartDate = new Date(); + fireCallParticipantChangeEvent( + CallParticipantChangeEvent.CALL_PARTICIPANT_STATE_CHANGE, + oldState, + newState); + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/mock/MockOperationSetBasicTelephony.java b/src/net/java/sip/communicator/impl/protocol/mock/MockOperationSetBasicTelephony.java new file mode 100644 index 0000000..f5f7263 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/mock/MockOperationSetBasicTelephony.java @@ -0,0 +1,284 @@ +package net.java.sip.communicator.impl.protocol.mock; + +import java.text.*; +import java.util.*; + +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.util.*; + +/** + * A mock implementation of a basic telephony opearation set + * + * @author Damian Minkov + */ +public class MockOperationSetBasicTelephony + implements OperationSetBasicTelephony, + CallChangeListener +{ + private static final Logger logger + = Logger.getLogger(MockOperationSetBasicTelephony.class); + + /** + * A list of listeners registered for + * <tt>CallEvent</tt>s. + */ + private Vector callListeners = new Vector(); + + /** + * A reference to the <tt>ProtocolProviderServiceSipImpl</tt> instance + * that created us. + */ + private MockProvider protocolProvider = null; + + /** + * A table mapping call ids against call instances. + */ + private Hashtable activeCalls = new Hashtable(); + + + public MockOperationSetBasicTelephony(MockProvider protocolProvider) + { + this.protocolProvider = protocolProvider; + } + + /** + * Registers the specified CallListener with this provider so that it + * could be notified when incoming calls are received. + * + * @param listener the listener to register with this provider. + */ + public void addCallListener(CallListener listener) + { + synchronized(callListeners){ + callListeners.add(listener); + } + } + + /** + * Indicates a user request to answer an incoming call from the specified + * CallParticipant. + * + * @param participant the call participant that we'd like to anwer. + * @throws OperationFailedException with the corresponding code if we + * encounter an error while performing this operation. + */ + public void answerCallParticipant(CallParticipant participant) throws + OperationFailedException + { + MockCallParticipant callParticipant + = (MockCallParticipant)participant; + if(participant.getState().equals(CallParticipantState.CONNECTED)) + { + logger.info("Ignoring user request to answer a CallParticipant " + + "that is already connected. CP:" + participant); + return; + } + + callParticipant.setState(CallParticipantState.CONNECTED, null); + } + + /** + * Create a new call and invite the specified CallParticipant to it. + * + * @param uri the address of the callee that we should invite to a new + * call. + * @return CallParticipant the CallParticipant that will represented by + * the specified uri. All following state change events will be + * delivered through that call participant. The Call that this + * participant is a member of could be retrieved from the + * CallParticipatn instance with the use of the corresponding method. + * @throws OperationFailedException with the corresponding code if we + * fail to create the call. + * @throws ParseException if <tt>callee</tt> is not a valid sip address + * string. + */ + public Call createCall(String uri) throws OperationFailedException, + ParseException + { + return createNewCall(uri); + } + + /** + * Create a new call and invite the specified CallParticipant to it. + * + * @param callee the address of the callee that we should invite to a + * new call. + * @return CallParticipant the CallParticipant that will represented by + * the specified uri. All following state change events will be + * delivered through that call participant. The Call that this + * participant is a member of could be retrieved from the + * CallParticipatn instance with the use of the corresponding method. + * @throws OperationFailedException with the corresponding code if we + * fail to create the call. + */ + public Call createCall(Contact callee) throws OperationFailedException + { + return createNewCall(callee.getAddress()); + } + + private Call createNewCall(String address) + { + MockCall newCall = new MockCall(protocolProvider); + + newCall.addCallChangeListener(this); + activeCalls.put(newCall.getCallID(), newCall); + + new MockCallParticipant(address, newCall); + + return newCall; + } + + /** + * Returns an iterator over all currently active calls. + * + * @return Iterator + */ + public Iterator getActiveCalls() + { + return activeCalls.values().iterator(); + } + + /** + * Indicates a user request to end a call with the specified call + * particiapnt. + * + * @param participant the participant that we'd like to hang up on. + * @throws OperationFailedException with the corresponding code if we + * encounter an error while performing this operation. + */ + public void hangupCallParticipant(CallParticipant participant) throws + OperationFailedException + { + //do nothing if the call is already ended + if (participant.getState().equals(CallParticipantState.DISCONNECTED)) + { + logger.debug("Ignoring a request to hangup a call participant " + +"that is already DISCONNECTED"); + return; + } + + MockCallParticipant callParticipant + = (MockCallParticipant)participant; + + logger.info("hangupCallParticipant"); + callParticipant.setState(CallParticipantState.DISCONNECTED, null); + } + + /** + * Resumes communication with a call participant previously put on hold. + * + * @param participant the call participant to put on hold. + * @todo Implement this + * net.java.sip.communicator.service.protocol.OperationSetBasicTelephony + * method + */ + public void putOffHold(CallParticipant participant) + { + + } + + /** + * Puts the specified CallParticipant "on hold". + * + * @param participant the participant that we'd like to put on hold. + * @throws OperationFailedException with the corresponding code if we + * encounter an error while performing this operation. + * @todo Implement this + * net.java.sip.communicator.service.protocol.OperationSetBasicTelephony + * method + */ + public void putOnHold(CallParticipant participant) throws + OperationFailedException + { + } + + /** + * Removes the specified listener from the list of call listeners. + * + * @param listener the listener to unregister. + */ + public void removeCallListener(CallListener listener) + { + synchronized(callListeners){ + callListeners.remove(listener); + } + } + + public Call receiveCall(String fromAddress) + throws Exception + { + Call newCall = createCall(fromAddress); + fireCallEvent(CallEvent.CALL_RECEIVED, newCall); + + return newCall; + } + + public Call placeCall(String toAddress) + throws Exception + { + Call newCall = createCall(toAddress); + fireCallEvent(CallEvent.CALL_INITIATED, newCall); + return newCall; + } + + /** + * Creates and dispatches a <tt>CallEvent</tt> notifying registered + * listeners that an event with id <tt>eventID</tt> has occurred on + * <tt>sourceCall</tt>. + * + * @param eventID the ID of the event to dispatch + * @param sourceCall the call on which the event has occurred. + */ + protected void fireCallEvent( int eventID, + Call sourceCall) + { + CallEvent cEvent = new CallEvent(sourceCall, eventID); + + logger.debug("Dispatching a CallEvent to " + + callListeners.size() + +" listeners. event is: " + cEvent.toString()); + + Iterator listeners = new ArrayList(callListeners).iterator(); + + while(listeners.hasNext()) + { + CallListener listener = (CallListener)listeners.next(); + + if(eventID == CallEvent.CALL_INITIATED) + listener.outgoingCallCreated(cEvent); + else if(eventID == CallEvent.CALL_RECEIVED) + listener.incomingCallReceived(cEvent); + else if(eventID == CallEvent.CALL_ENDED) + listener.callEnded(cEvent); + } + } + + public CallParticipant addNewCallParticipant(Call call, String address) + { + return new MockCallParticipant(address, (MockCall)call); + } + + public void callParticipantAdded(CallParticipantEvent evt) + { + } + + public void callParticipantRemoved(CallParticipantEvent evt) + { + } + + public void callStateChanged(CallChangeEvent evt) + { + if(evt.getEventType().equals(CallChangeEvent.CALL_STATE_CHANGE) + && ((CallState)evt.getNewValue()).equals(CallState.CALL_ENDED)) + { + MockCall sourceCall = (MockCall)this.activeCalls + .remove(evt.getSourceCall().getCallID()); + + logger.trace( "Removing call " + sourceCall + " from the list of " + + "active calls because it entered an ENDED state"); + + fireCallEvent(CallEvent.CALL_ENDED, sourceCall); + } + } +} diff --git a/src/net/java/sip/communicator/service/callhistory/CallHistoryService.java b/src/net/java/sip/communicator/service/callhistory/CallHistoryService.java index 6e26a71..01153b9 100644 --- a/src/net/java/sip/communicator/service/callhistory/CallHistoryService.java +++ b/src/net/java/sip/communicator/service/callhistory/CallHistoryService.java @@ -9,6 +9,7 @@ package net.java.sip.communicator.service.callhistory; import java.util.*; import net.java.sip.communicator.service.contactlist.*; +import net.java.sip.communicator.service.callhistory.event.*; /** * The Call History Service stores info about calls made from various protocols @@ -55,6 +56,38 @@ public interface CallHistoryService Collection findByPeriod(MetaContact contact, Date startDate, Date endDate) throws RuntimeException; + + /** + * Returns all the calls made after the given date + * + * @param startDate Date the start date of the calls + * @return Collection of CallReceivedEvent + * @throws RuntimeException + */ + Collection findByStartDate(Date startDate) + throws RuntimeException; + + /** + * Returns all the calls made before the given date + * + * @param endDate Date the end date of the calls + * @return Collection of CallReceivedEvent + * @throws RuntimeException + */ + Collection findByEndDate(Date endDate) + throws RuntimeException; + + /** + * Returns all the calls made between the given dates + * + * @param startDate Date the start date of the calls + * @param endDate Date the end date of the calls + * @return Collection of CallReceivedEvent + * @throws RuntimeException + */ + Collection findByPeriod(Date startDate, Date endDate) + throws RuntimeException; + /** * Returns the supplied number of recent calls made by all the contacts * in the supplied metacontact @@ -67,4 +100,29 @@ public interface CallHistoryService Collection findLast(MetaContact contact, int count) throws RuntimeException; + /** + * Returns the supplied number of recent calls made by all the contacts + * in the supplied metacontact + * + * @param count calls count + * @return Collection of CallReceivedEvent + * @throws RuntimeException + */ + Collection findLast(int count) + throws RuntimeException; + + + /** + * Adding progress listener for monitoring progress of search process + * + * @param listener HistorySearchProgressListener + */ + void addSearchProgressListener(CallHistorySearchProgressListener listener); + + /** + * Removing progress listener + * + * @param listener HistorySearchProgressListener + */ + void removeSearchProgressListener(CallHistorySearchProgressListener listener); } diff --git a/src/net/java/sip/communicator/service/callhistory/CallParticipantRecord.java b/src/net/java/sip/communicator/service/callhistory/CallParticipantRecord.java new file mode 100644 index 0000000..942c929 --- /dev/null +++ b/src/net/java/sip/communicator/service/callhistory/CallParticipantRecord.java @@ -0,0 +1,88 @@ +package net.java.sip.communicator.service.callhistory; + +import java.util.*; + +/** + * Structure used for encapsulating data when writing or reading + * Call History Data. Also These records are uesd for returning data + * from the Call History Service + * + * @author Damian Minkov + */ +public class CallParticipantRecord +{ + private String participantAddress = null; + private Date startTime = null; + private Date endTime = null; + + /** + * Creates CallParticipantRecord + * @param participantAddress String + * @param startTime Date + * @param endTime Date + */ + public CallParticipantRecord( + String participantAddress, + Date startTime, + Date endTime) + { + this.participantAddress = participantAddress; + this.startTime = startTime; + this.endTime = endTime; + } + + /** + * When participant diconnected from the call + * + * @return Date + */ + public Date getEndTime() + { + return endTime; + } + + /** + * The participant address + * @return String + */ + public String getParticipantAddress() + { + return participantAddress; + } + + /** + * When participant connected to the call + * @return Date + */ + public Date getStartTime() + { + return startTime; + } + + /** + * Sets the time the participant joined the call + * @param startTime Date + */ + public void setStartTime(Date startTime) + { + this.startTime = startTime; + } + + /** + * Sets the particiapnts address + * @param participantAddress String + */ + public void setParticipantAddress(String participantAddress) + { + this.participantAddress = participantAddress; + } + + /** + * Sets the time participant leaves the call + * @param endTime Date + */ + public void setEndTime(Date endTime) + { + this.endTime = endTime; + } +} diff --git a/src/net/java/sip/communicator/service/callhistory/CallRecord.java b/src/net/java/sip/communicator/service/callhistory/CallRecord.java new file mode 100644 index 0000000..af6f069 --- /dev/null +++ b/src/net/java/sip/communicator/service/callhistory/CallRecord.java @@ -0,0 +1,163 @@ +package net.java.sip.communicator.service.callhistory; + +import java.util.*; + +import net.java.sip.communicator.service.protocol.*; + +/** + * Structure used for encapsulating data when writing or reading + * Call History Data. Also These records are uesd for returning data + * from the Call History Service + * + * @author Damian Minkov + */ +public class CallRecord +{ + /** + * Possible directions of the call + */ + public final static String OUT = "out"; + public final static String IN = "in"; + + private Call sourceCall = null; + private String direction = null; + private Vector participantRecords = new Vector(); + private Date startTime = null; + private Date endTime = null; + + /** + * Creates CallRecord + */ + public CallRecord() + { + } + + /** + * Creates Call Record + * @param sourceCall Call + * @param direction String + * @param startTime Date + * @param endTime Date + */ + public CallRecord( + Call sourceCall, + String direction, + Date startTime, + Date endTime) + { + this.sourceCall = sourceCall; + this.direction = direction; + this.startTime = startTime; + this.endTime = endTime; + } + + /** + * Finds a Participant with the supplied address + * @param address String + * @return CallParticipantRecord + */ + public CallParticipantRecord findParticipantRecord(String address) + { + Iterator iter = participantRecords.iterator(); + while (iter.hasNext()) + { + CallParticipantRecord item = (CallParticipantRecord) iter.next(); + if (item.getParticipantAddress().equals(address)) + return item; + } + + return null; + } + + /** + * Set the time when the call finishes + * If some participant has no end Time set we set it also + * @param endTime Date + */ + public void setEndTime(Date endTime) + { + this.endTime = endTime; + + Iterator iter = participantRecords.iterator(); + while (iter.hasNext()) + { + CallParticipantRecord item = (CallParticipantRecord) iter.next(); + if(item.getEndTime() == null) + item.setEndTime(endTime); + } + } + + /** + * Sets the time when the call begins + * @param startTime Date + */ + public void setStartTime(Date startTime) + { + this.startTime = startTime; + } + + /** + * The source call which this record servers + * @param sourceCall Call + */ + public void setSourceCall(Call sourceCall) + { + this.sourceCall = sourceCall; + } + + /** + * Sets the direction of the call + * IN or OUT + * @param direction String + */ + public void setDirection(String direction) + { + this.direction = direction; + } + + /** + * Returns the direction of the call + * IN or OUT + * @return String + */ + public String getDirection() + { + return direction; + } + + /** + * Returns the time when the call has finished + * @return Date + */ + public Date getEndTime() + { + return endTime; + } + + /** + * Return Vector of CallParticipantRecords + * @return Vector + */ + public Vector getParticipantRecords() + { + return participantRecords; + } + + /** + * The Call source of this record + * @return Call + */ + public Call getSourceCall() + { + return sourceCall; + } + + /** + * The time when the call has began + * @return Date + */ + public Date getStartTime() + { + return startTime; + } +} diff --git a/src/net/java/sip/communicator/service/callhistory/event/CallHistorySearchProgressListener.java b/src/net/java/sip/communicator/service/callhistory/event/CallHistorySearchProgressListener.java new file mode 100644 index 0000000..579d400 --- /dev/null +++ b/src/net/java/sip/communicator/service/callhistory/event/CallHistorySearchProgressListener.java @@ -0,0 +1,40 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.service.callhistory.event; + +import net.java.sip.communicator.service.history.event.*; + +/** + * When searching into the call history a ProgressEvent is fired whenever + * the progress is changed. Its fired through the search process + * informing us about the current progress. + * + * @author Damian Minkov + */ +public interface CallHistorySearchProgressListener +{ + /** + * The minimum value for the progress change. + * This is value indicates that the process has started. + */ + public static int PROGRESS_MINIMUM_VALUE = + HistorySearchProgressListener.PROGRESS_MINIMUM_VALUE; + + /** + * The maximum value for the progress change. + * This is value indicates that the process is finished. + */ + public static int PROGRESS_MAXIMUM_VALUE = + HistorySearchProgressListener.PROGRESS_MAXIMUM_VALUE; + + /** + * This method gets called when progress changes through the search process + * @param evt ProgressEvent the event holding the search condition and + * the current progress value. + */ + void progressChanged(ProgressEvent evt); +} diff --git a/src/net/java/sip/communicator/service/callhistory/event/ProgressEvent.java b/src/net/java/sip/communicator/service/callhistory/event/ProgressEvent.java new file mode 100644 index 0000000..ed8e58f --- /dev/null +++ b/src/net/java/sip/communicator/service/callhistory/event/ProgressEvent.java @@ -0,0 +1,74 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.service.callhistory.event; + +import java.util.*; + +/** + * A "ProgressEvent" event gets delivered through the search process + * of CallHistoryService Service. + * The event is wrapper around the generated event from the History Service + * + * @author Damian Minkov + */ +public class ProgressEvent + extends java.util.EventObject +{ + private net.java.sip.communicator.service.history.event.ProgressEvent evt; + + /** + * The current progress that we will pass. + */ + private int progress = 0; + + public ProgressEvent( + Object source, + net.java.sip.communicator.service.history.event.ProgressEvent evt, + int progress) + { + super(source); + + this.evt = evt; + this.progress = progress; + } + + /** + * Gets the current progress that will be fired. + * @return int the progress value + */ + public int getProgress() + { + return progress; + } + + /** + * The end date in the search condition. + * @return Date end date value + */ + public Date getEndDate() + { + return evt.getEndDate(); + } + + /** + * The start date in the search condition. + * @return Date start date value + */ + public Date getStartDate() + { + return evt.getStartDate(); + } + + /** + * Sets the progress that will be fired + * @param progress int progress value + */ + public void setProgress(int progress) + { + this.progress = progress; + } +} diff --git a/test/net/java/sip/communicator/slick/callhistory/CallHistoryServiceLick.java b/test/net/java/sip/communicator/slick/callhistory/CallHistoryServiceLick.java new file mode 100644 index 0000000..8ccea16 --- /dev/null +++ b/test/net/java/sip/communicator/slick/callhistory/CallHistoryServiceLick.java @@ -0,0 +1,56 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.slick.callhistory; + +import java.util.*; + +import org.osgi.framework.*; +import junit.framework.*; +import net.java.sip.communicator.util.*; + +/** + * + * @author Damian Minkov + */ +public class CallHistoryServiceLick extends TestSuite implements BundleActivator { + private static Logger logger = Logger.getLogger(CallHistoryServiceLick.class); + + protected static BundleContext bc = null; + + /** + * Start the History Sevice Implementation Compatibility Kit. + * + * @param bundleContext + * BundleContext + * @throws Exception + */ + public void start(BundleContext bundleContext) + throws Exception + { + CallHistoryServiceLick.bc = bundleContext; + + setName("CallHistoryServiceLick"); + Hashtable properties = new Hashtable(); + properties.put("service.pid", getName()); + + addTest(TestCallHistoryService.suite()); + bundleContext.registerService(getClass().getName(), this, properties); + + logger.debug("Successfully registered " + getClass().getName()); + } + + /** + * stop + * + * @param bundlecontext BundleContext + * @throws Exception + */ + public void stop(BundleContext bundlecontext) + throws Exception + { + } +} diff --git a/test/net/java/sip/communicator/slick/callhistory/TestCallHistoryService.java b/test/net/java/sip/communicator/slick/callhistory/TestCallHistoryService.java new file mode 100644 index 0000000..9766180 --- /dev/null +++ b/test/net/java/sip/communicator/slick/callhistory/TestCallHistoryService.java @@ -0,0 +1,353 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.slick.callhistory; + +import java.util.*; + +import org.osgi.framework.*; +import junit.framework.*; +import net.java.sip.communicator.impl.protocol.mock.*; +import net.java.sip.communicator.service.callhistory.*; +import net.java.sip.communicator.service.contactlist.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.util.*; + +/** + * Tests call history. + * First installs the MockProtocolProvider to be able to create som calls + * The call history service stores them + * and then tests the verious find methods - does they find the calls we have + * already made + * + * @author Damian Minkov + */ +public class TestCallHistoryService + extends TestCase +{ + private static final Logger logger = Logger.getLogger(TestCallHistoryService.class); + + /** + * The provider that we use to make a dummy server-stored contactlist + * used for testing. The mockProvider is instantiated and registered + * by the metacontactlist slick activator. + */ + public static MockProvider mockProvider = null; + + public static MockOperationSetBasicTelephony mockBTelphonyOpSet = null; + + private static ServiceReference callHistoryServiceRef = null; + public static CallHistoryService callHistoryService = null; + + /** + * A reference to the registration of the first mock provider. + */ + public static ServiceRegistration mockPrServiceRegistration = null; + + private static Date controlDate1 = null; + private static Date controlDate2 = null; + + /** + * The addresses will be used in the generated mock calls + */ + private static Vector participantAddresses = new Vector(); + + public TestCallHistoryService(String name) + { + super(name); + } + + public static Test suite() + { + TestSuite suite = new TestSuite(); + suite.addTest( + new TestCallHistoryService("setupContact")); + suite.addTest( + new TestCallHistoryService("writeRecords")); + suite.addTest( + new TestCallHistoryService("readRecords")); + suite.addTest( + new TestCallHistoryService("checkRecordCompleteness")); + + return suite; + } + + protected void setUp() throws Exception + { + } + + protected void tearDown() throws Exception + { + } + + public void setupContact() + { + // changes the history service target derictory + System.setProperty("HistoryServiceDirectory", "test-callhistory"); + + mockProvider = new MockProvider("CallHistoryMockUser"); + + //store thre presence op set of the new provider into the fixture + Map supportedOperationSets = + mockProvider.getSupportedOperationSets(); + + mockBTelphonyOpSet = + (MockOperationSetBasicTelephony) supportedOperationSets.get( + OperationSetBasicTelephony.class.getName()); + + System.setProperty(MetaContactListService.PROVIDER_MASK_PROPERTY, "1"); + + Hashtable mockProvProperties = new Hashtable(); + mockProvProperties.put(ProtocolProviderFactory.PROTOCOL + , mockProvider.getProtocolName()); + mockProvProperties.put(MetaContactListService.PROVIDER_MASK_PROPERTY, + "1"); + + mockPrServiceRegistration = + CallHistoryServiceLick.bc.registerService( + ProtocolProviderService.class.getName(), + mockProvider, + mockProvProperties); + logger.debug("Registered a mock protocol provider! "); + + callHistoryServiceRef = + CallHistoryServiceLick.bc. + getServiceReference(CallHistoryService.class.getName()); + + callHistoryService = + (CallHistoryService) CallHistoryServiceLick.bc. + getService(callHistoryServiceRef); + + // Will genarate 4 Calls with 4 different participants + participantAddresses.add("participant_address_1"); + participantAddresses.add("participant_address_2"); + participantAddresses.add("participant_address_3"); + participantAddresses.add("participant_address_4"); + } + + /** + * First create calls + */ + public void writeRecords() + { + logger.info("write records "); + + generateCall((String)participantAddresses.get(0)); + + controlDate1 = new Date(); + + generateCall((String)participantAddresses.get(1)); + + generateCall((String)participantAddresses.get(2)); + + controlDate2 = new Date(); + + generateCall((String)participantAddresses.get(3)); + } + + private void generateCall(String participant) + { + try + { + Call newCall = mockBTelphonyOpSet.placeCall(participant); + + Vector v = new Vector(); + + Iterator iter = newCall.getCallParticipants(); + while (iter.hasNext()) + { + CallParticipant item = (CallParticipant) iter.next(); + v.add(item); + } + + waitSeconds(2000); + + iter = v.iterator(); + while (iter.hasNext()) + { + CallParticipant item = (CallParticipant) iter.next(); + mockBTelphonyOpSet.hangupCallParticipant(item); + } + } + catch (Exception ex1) + { + logger.error("Cannot place mock call", ex1); + fail("Cannot place mock call to " + participant); + } + } + + + private void waitSeconds(long secs) + { + Object lock = new Object(); + synchronized (lock){ + // wait a moment + try{ + lock.wait(secs); + } + catch (InterruptedException ex){} + } + } + + /** + * tests all read methods (finders) + */ + public void readRecords() + { + /** + * This must match also many calls, as tests are run many times + * but the minimum is 3 + */ + Collection rs = callHistoryService.findByEndDate(controlDate2); + Iterator resultIter = rs.iterator(); + + assertTrue("Calls too few - findByEndDate", rs.size() >= 3); + + /** + * must find 2 calls + */ + rs = callHistoryService.findByPeriod(controlDate1, controlDate2); + resultIter = rs.iterator(); + + assertEquals("Calls must be 2", rs.size(), 2); + + CallRecord rec = (CallRecord)resultIter.next(); + CallParticipantRecord participant = + (CallParticipantRecord)rec.getParticipantRecords().get(0); + + assertTrue("Participant incorrect ", + participant.getParticipantAddress(). + equals(participantAddresses.get(1))); + + rec = (CallRecord)resultIter.next(); + participant = (CallParticipantRecord)rec.getParticipantRecords().get(0); + + assertTrue("Participant incorrect ", + participant.getParticipantAddress(). + equals(participantAddresses.get(2))); + + /** + * must find 1 record + */ + rs = callHistoryService.findByStartDate(controlDate2); + resultIter = rs.iterator(); + + assertEquals("Calls must be 1", rs.size(), 1); + + rec = (CallRecord)resultIter.next(); + participant = (CallParticipantRecord)rec.getParticipantRecords().get(0); + + assertTrue("Participant incorrect ", + participant.getParticipantAddress(). + equals(participantAddresses.get(3))); + + /** + * Must return exactly the last 3 calls + */ + rs = callHistoryService.findLast(3); + resultIter = rs.iterator(); + + assertEquals("Calls must be 3", rs.size(), 3); + + rec = (CallRecord)resultIter.next(); + participant = (CallParticipantRecord) rec.getParticipantRecords().get(0); + + assertTrue("Participant incorrect ", + participant.getParticipantAddress(). + equals(participantAddresses.get(1))); + + rec = (CallRecord)resultIter.next(); + participant = (CallParticipantRecord) rec.getParticipantRecords().get(0); + + assertTrue("Participant incorrect ", + participant.getParticipantAddress(). + equals(participantAddresses.get(2))); + + rec = (CallRecord)resultIter.next(); + participant = (CallParticipantRecord) rec.getParticipantRecords().get(0); + + assertTrue("Participant incorrect ", + participant.getParticipantAddress(). + equals(participantAddresses.get(3))); + } + + public void checkRecordCompleteness() + { + Vector participantAddresses = new Vector(); + participantAddresses.add("some_address"); + participantAddresses.add("some_new_address"); + + try + { + Call newCall = + mockBTelphonyOpSet.placeCall((String)participantAddresses.get(0)); + + Vector v = new Vector(); + + Iterator iter = newCall.getCallParticipants(); + while (iter.hasNext()) + { + CallParticipant item = (CallParticipant) iter.next(); + v.add(item); + } + + waitSeconds(2000); + + CallParticipant newParticipant = + mockBTelphonyOpSet.addNewCallParticipant(newCall, + (String)participantAddresses.get(1)); + + mockBTelphonyOpSet.hangupCallParticipant(newParticipant); + + waitSeconds(2000); + + iter = v.iterator(); + while (iter.hasNext()) + { + CallParticipant item = (CallParticipant) iter.next(); + mockBTelphonyOpSet.hangupCallParticipant(item); + } + } + catch (Exception ex1) + { + logger.error("Cannot place mock call", ex1); + fail("Cannot place mock call"); + } + + + Collection lastCall = callHistoryService.findLast(1); + + assertEquals("There must be 1 Call", lastCall.size(), 1); + + CallRecord callRecord = (CallRecord)lastCall.iterator().next(); + + assertEquals("There must be 2 participants in the call", + callRecord.getParticipantRecords().size(), 2); + + CallParticipantRecord callP1 = + callRecord.findParticipantRecord((String)participantAddresses.get(0)); + CallParticipantRecord callP2 = + callRecord.findParticipantRecord((String)participantAddresses.get(1)); + + assertTrue("Second participant added after first one", + callP2.getStartTime().after(callP1.getStartTime())); + + assertTrue("Second participant hanguped before first one", + callP2.getEndTime().before(callP1.getEndTime())); + } + + private void dumpResult(Collection c) + { + Iterator rs = c.iterator(); + while (rs.hasNext()) + { + CallRecord hr = (CallRecord)rs.next(); + logger.info("----------------------"); + logger.info(hr.getParticipantRecords()); + logger.info("----------------------"); + } + } +} diff --git a/test/net/java/sip/communicator/slick/callhistory/callhistory.slick.manifest.mf b/test/net/java/sip/communicator/slick/callhistory/callhistory.slick.manifest.mf new file mode 100755 index 0000000..c1fafe9 --- /dev/null +++ b/test/net/java/sip/communicator/slick/callhistory/callhistory.slick.manifest.mf @@ -0,0 +1,25 @@ +Bundle-Activator: net.java.sip.communicator.slick.callhistory.CallHistoryServiceLick +Bundle-Name: Call History Service Implementation Compatibility Kit +Bundle-Description: A Service Implementation Compatibility Kit for the History Service +Bundle-Vendor: sip-communicator.org +Bundle-Version: 0.0.1 +Import-Package: junit.framework, + net.java.sip.communicator.slick.history, + net.java.sip.communicator.service.history.records, + net.java.sip.communicator.service.history, + net.java.sip.communicator.service.contactlist, + net.java.sip.communicator.service.callhistory, + net.java.sip.communicator.service.callhistory.event, + net.java.sip.communicator.impl.protocol.mock, + net.java.sip.communicator.service.protocol, + net.java.sip.communicator.service.protocol.event, + org.osgi.framework, + org.w3c.dom, + javax.xml.parsers, + net.java.sip.communicator.util, + net.java.sip.communicator.util.xml, + javax.xml.transform, + javax.xml.transform.dom, + javax.xml.transform.stream, + org.apache.xml.serializer, +Export-Package: net.java.sip.communicator.slick.callhistory, |