diff --git a/bindings/java/src/org/sleuthkit/datamodel/Account.java b/bindings/java/src/org/sleuthkit/datamodel/Account.java
index e759d5b1a389b13221cacf4ef85c5a90c4e571ab..a5abf5908f679e2cba209a511c71b74010d17ba2 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/Account.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/Account.java
@@ -160,4 +160,37 @@ public Account.Type getAccountType() {
 	public long getAccountID() {
 		return this.account_id;
 	}
+
+	@Override
+	public int hashCode() {
+		int hash = 5;
+		hash = 43 * hash + (int) (this.account_id ^ (this.account_id >>> 32));
+		hash = 43 * hash + (this.accountType != null ? this.accountType.hashCode() : 0);
+		hash = 43 * hash + (this.typeSpecificID != null ? this.typeSpecificID.hashCode() : 0);
+		return hash;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj) {
+			return true;
+		}
+		if (obj == null) {
+			return false;
+		}
+		if (getClass() != obj.getClass()) {
+			return false;
+		}
+		final Account other = (Account) obj;
+		if (this.account_id != other.account_id) {
+			return false;
+		}
+		if ((this.typeSpecificID == null) ? (other.typeSpecificID != null) : !this.typeSpecificID.equals(other.typeSpecificID)) {
+			return false;
+		}
+		if (this.accountType != other.accountType && (this.accountType == null || !this.accountType.equals(other.accountType))) {
+			return false;
+		}
+		return true;
+	}
 }
diff --git a/bindings/java/src/org/sleuthkit/datamodel/AccountDeviceInstance.java b/bindings/java/src/org/sleuthkit/datamodel/AccountDeviceInstance.java
index 226e524a5defd1d682340718badcc8135893f09e..2e16b06484214bc79af982756b3e176e130cfafe 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/AccountDeviceInstance.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/AccountDeviceInstance.java
@@ -50,4 +50,35 @@ public Account getAccount(){
 	public String getDeviceId(){
 		return this.deviceID;
 	}
+
+	@Override
+	public int hashCode() {
+		int hash = 5;
+		hash = 11 * hash + (this.account != null ? this.account.hashCode() : 0);
+		hash = 11 * hash + (this.deviceID != null ? this.deviceID.hashCode() : 0);
+		return hash;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj) {
+			return true;
+		}
+		if (obj == null) {
+			return false;
+		}
+		if (getClass() != obj.getClass()) {
+			return false;
+		}
+		final AccountDeviceInstance other = (AccountDeviceInstance) obj;
+		if ((this.deviceID == null) ? (other.deviceID != null) : !this.deviceID.equals(other.deviceID)) {
+			return false;
+		}
+		if (this.account != other.account && (this.account == null || !this.account.equals(other.account))) {
+			return false;
+		}
+		return true;
+	}
+	
+	
 }
diff --git a/bindings/java/src/org/sleuthkit/datamodel/AccountPair.java b/bindings/java/src/org/sleuthkit/datamodel/AccountPair.java
new file mode 100644
index 0000000000000000000000000000000000000000..6bae624820b1503f0367e044c24a6718bad03fb9
--- /dev/null
+++ b/bindings/java/src/org/sleuthkit/datamodel/AccountPair.java
@@ -0,0 +1,61 @@
+/*
+ * SleuthKit Java Bindings
+ *
+ * Copyright 2018 Basis Technology Corp.
+ * Contact: carrier <at> sleuthkit <dot> org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.sleuthkit.datamodel;
+
+/**
+ * Class representing an unordered pair of account device instances. <a,b> is
+ * same as <b,a>
+ */
+public final class AccountPair {
+
+	private final AccountDeviceInstance account1;
+	private final AccountDeviceInstance account2;
+
+	public AccountDeviceInstance getFirst() {
+		return account1;
+	}
+
+	public AccountDeviceInstance getSecond() {
+		return account2;
+	}
+
+	AccountPair(AccountDeviceInstance account1, AccountDeviceInstance account2) {
+		this.account1 = account1;
+		this.account2 = account2;
+	}
+
+	@Override
+	public int hashCode() {
+		return account1.hashCode() + account2.hashCode();
+	}
+
+	@Override
+	public boolean equals(Object other) {
+		if (other == this) {
+			return true;
+		}
+		if (!(other instanceof AccountPair)) {
+			return false;
+		}
+		AccountPair otherPair = (AccountPair) other;
+		return (account1.equals(otherPair.account1) && account2.equals(otherPair.account2))
+				|| (account1.equals(otherPair.account2) && account2.equals(otherPair.account1));
+	}
+
+}
diff --git a/bindings/java/src/org/sleuthkit/datamodel/CommunicationsFilter.java b/bindings/java/src/org/sleuthkit/datamodel/CommunicationsFilter.java
index 959087ea77bd5dd9b764c81f00b299dee26e9fb3..06a46dc2ef6a11b4cd7aefc791ef0c8b8ca2dfcf 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/CommunicationsFilter.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/CommunicationsFilter.java
@@ -31,7 +31,7 @@
  * Defines an aggregate of filters to apply to a CommunicationsManager query.
  *
  */
-public class CommunicationsFilter {
+final public class CommunicationsFilter {
 
 	private final List<SubFilter> andFilters;
 	// RAMAN TBD: figure out OR filters, I don't think we need any
@@ -72,7 +72,7 @@ public void addAndFilter(SubFilter subFilter) {
 	/**
 	 * Unit level filter.
 	 */
-	public static abstract class SubFilter {
+	static abstract class SubFilter {
 
 		/**
 		 * Returns a string description of the filter.
@@ -95,7 +95,7 @@ public static abstract class SubFilter {
 	 * Filters communications by relationship type.
 	 *
 	 */
-	public static class RelationshipTypeFilter extends SubFilter {
+	final public static class RelationshipTypeFilter extends SubFilter {
 
 		private final Set<Relationship.Type> relationshipTypes;
 
@@ -135,7 +135,7 @@ public String getSQL(CommunicationsManager commsManager) {
 		}
 	}
 
-	public static class DateRangeFilter extends SubFilter {
+	final public static class DateRangeFilter extends SubFilter {
 
 		private final long startDate;
 		private final long endDate;
@@ -192,7 +192,7 @@ public String getSQL(CommunicationsManager commsManager) {
 	 * Filter communications by account type.
 	 *
 	 */
-	public static class AccountTypeFilter extends SubFilter {
+	final public static class AccountTypeFilter extends SubFilter {
 
 		private final Set<Account.Type> accountTypes;
 
@@ -237,7 +237,7 @@ public String getSQL(CommunicationsManager commsManager) {
 	 * Filter by device ids.
 	 *
 	 */
-	public static class DeviceFilter extends SubFilter {
+	final public static class DeviceFilter extends SubFilter {
 
 		private final Set<String> deviceIds;
 
diff --git a/bindings/java/src/org/sleuthkit/datamodel/CommunicationsManager.java b/bindings/java/src/org/sleuthkit/datamodel/CommunicationsManager.java
index 6ab357a18c33c75af0a4de967239f2376c4bbb02..bafd77e5f7b0944023c1d30294b7d9bc82c65dcf 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/CommunicationsManager.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/CommunicationsManager.java
@@ -1,7 +1,7 @@
 /*
  * Sleuth Kit Data Model
  *
- * Copyright 2017 Basis Technology Corp.
+ * Copyright 2017-18 Basis Technology Corp.
  * Contact: carrier <at> sleuthkit <dot> org
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -27,7 +27,6 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -178,8 +177,8 @@ SleuthkitCase getSleuthkitCase() {
 
 	/**
 	 * Add a custom account type that is not already defined in Account.Type.
-     * Will not allow duplicates and will return existing type if the name is
-     * already defined.
+	 * Will not allow duplicates and will return existing type if the name is
+	 * already defined.
 	 *
 	 * @param accountTypeName account type that must be unique
 	 * @param displayName     account type display name
@@ -189,7 +188,7 @@ SleuthkitCase getSleuthkitCase() {
 	 * @throws TskCoreException exception thrown if a critical error occurs
 	 *                          within TSK core
 	 */
-    // NOTE: Full name given for Type for doxygen linking
+	// NOTE: Full name given for Type for doxygen linking
 	public org.sleuthkit.datamodel.Account.Type addAccountType(String accountTypeName, String displayName) throws TskCoreException {
 		Account.Type accountType = new Account.Type(accountTypeName, displayName);
 
@@ -260,7 +259,7 @@ public org.sleuthkit.datamodel.Account.Type addAccountType(String accountTypeNam
 	 * @throws TskCoreException exception thrown if a critical error occurs
 	 *                          within TSK core
 	 */
-    // NOTE: Full name given for Type for doxygen linking
+	// NOTE: Full name given for Type for doxygen linking
 	public AccountFileInstance createAccountFileInstance(org.sleuthkit.datamodel.Account.Type accountType, String accountUniqueID, String moduleName, Content sourceFile) throws TskCoreException {
 
 		// make or get the Account (unique at the case-level)
@@ -295,7 +294,7 @@ public AccountFileInstance createAccountFileInstance(org.sleuthkit.datamodel.Acc
 	 * @throws TskCoreException exception thrown if a critical error occurs
 	 *                          within TSK core
 	 */
-    // NOTE: Full name given for Type for doxygen linking
+	// NOTE: Full name given for Type for doxygen linking
 	public Account getAccount(org.sleuthkit.datamodel.Account.Type accountType, String accountUniqueID) throws TskCoreException {
 		Account account = null;
 		CaseDbConnection connection = db.getConnection();
@@ -338,17 +337,16 @@ public Account getAccount(org.sleuthkit.datamodel.Account.Type accountType, Stri
 //
 //		return accountInstance;
 //	}
-	
 	/**
-	 * Add one or more relationships between the sender and recipient account instances. All
-	 * account instances must be from the same data source.
+	 * Add one or more relationships between the sender and recipient account
+	 * instances. All account instances must be from the same data source.
 	 *
-	 * @param sender               sender account
-	 * @param recipients           list of recipients
-	 * @param sourceArtifact       Artifact that relationships were derived from
-	 * @param relationshipType     The type of relationships to be created
-	 * @param dateTime             Date of communications/relationship, as epoch
-	 *                             seconds
+	 * @param sender           sender account
+	 * @param recipients       list of recipients
+	 * @param sourceArtifact   Artifact that relationships were derived from
+	 * @param relationshipType The type of relationships to be created
+	 * @param dateTime         Date of communications/relationship, as epoch
+	 *                         seconds
 	 *
 	 *
 	 * @throws org.sleuthkit.datamodel.TskCoreException
@@ -360,7 +358,7 @@ public Account getAccount(org.sleuthkit.datamodel.Account.Type accountType, Stri
 	 *                                                  relationshipType are not
 	 *                                                  compatible.
 	 */
-    // NOTE: Full name given for Type for doxygen linking
+	// NOTE: Full name given for Type for doxygen linking
 	public void addRelationships(AccountFileInstance sender, List<AccountFileInstance> recipients,
 			BlackboardArtifact sourceArtifact, org.sleuthkit.datamodel.Relationship.Type relationshipType, long dateTime) throws TskCoreException, TskDataException {
 
@@ -393,17 +391,15 @@ public void addRelationships(AccountFileInstance sender, List<AccountFileInstanc
 			}
 		}
 
-		Set<UnorderedAccountPair> relationships = listToUnorderedPairs(accountIDs);
-		Iterator<UnorderedAccountPair> iter = relationships.iterator();
-
-		while (iter.hasNext()) {
-			try {
-				UnorderedAccountPair accountPair = iter.next();
-				addAccountsRelationship(accountPair.getFirst(), accountPair.getSecond(),
-						sourceArtifact, relationshipType, dateTime);
-			} catch (TskCoreException ex) {
-                // @@@ This should probably not be caught and instead we stop adding
-				LOGGER.log(Level.WARNING, "Error adding relationship", ex); //NON-NLS
+		for (int i = 0; i < accountIDs.size(); i++) {
+			for (int j = i + 1; j < accountIDs.size(); j++) {
+				try {
+					addAccountsRelationship(accountIDs.get(i), accountIDs.get(j),
+							sourceArtifact, relationshipType, dateTime);
+				} catch (TskCoreException ex) {
+					// @@@ This should probably not be caught and instead we stop adding
+					LOGGER.log(Level.WARNING, "Error adding relationship", ex); //NON-NLS
+				}
 			}
 		}
 	}
@@ -469,7 +465,8 @@ private Account getOrCreateAccount(Account.Type accountType, String accountUniqu
 	 *
 	 * @param accountType     account type
 	 * @param accountUniqueID Unique account ID (such as email address)
-     * @param moduleName        module name that found this instance (for the artifact)
+	 * @param moduleName      module name that found this instance (for the
+	 *                        artifact)
 	 * @param sourceFile		    Source file (for the artifact)
 	 *
 	 * @return blackboard artifact for the instance
@@ -566,12 +563,12 @@ private BlackboardArtifact getAccountFileInstanceArtifact(Account.Type accountTy
 	 * @throws TskCoreException If an error occurs accessing the case database.
 	 *
 	 */
-    // NOTE: Full name given for Type for doxygen linking
+	// NOTE: Full name given for Type for doxygen linking
 	public org.sleuthkit.datamodel.Account.Type getAccountType(String accountTypeName) throws TskCoreException {
 		if (this.typeNameToAccountTypeMap.containsKey(accountTypeName)) {
 			return this.typeNameToAccountTypeMap.get(accountTypeName);
 		}
-        
+
 		CaseDbConnection connection = db.getConnection();
 		db.acquireSingleUserCaseReadLock();
 		Statement s = null;
@@ -663,7 +660,7 @@ private void addAccountsRelationship(long account1_id, long account2_id, Blackbo
 
 		try {
 			String dateTimeValStr = (dateTime > 0) ? Long.toString(dateTime) : "NULL";
-			
+
 			connection.beginTransaction();
 			s = connection.createStatement();
 			String query = "INTO account_relationships (account1_id, account2_id, relationship_source_obj_id, date_time, relationship_type, data_source_obj_id  ) "
@@ -692,8 +689,8 @@ private void addAccountsRelationship(long account1_id, long account2_id, Blackbo
 	}
 
 	/**
-	 * Returns a list of AccountDeviceInstances that at least one relationship that meets
-     * the criteria listed in the filters.
+	 * Returns a list of AccountDeviceInstances that at least one relationship
+	 * that meets the criteria listed in the filters.
 	 *
 	 * Applicable filters: DeviceFilter, AccountTypeFilter, DateRangeFilter,
 	 * RelationshipTypeFilter
@@ -798,9 +795,184 @@ public List<AccountDeviceInstance> getAccountDeviceInstancesWithRelationships(Co
 	}
 
 	/**
-	 * Get the number of unique relationship sources (such as EMAIL artifacts) associated with
-     * an account on a given device (AccountDeviceInstance) that meet the filter criteria.
-     *
+	 * Get the number of relationships between all pairs of accounts in the
+	 * given set. For each pair of accounts <a2,a1> == <a1,a2>, find the number
+	 * of relationships that pass the given filter, between those two accounts.
+	 *
+	 * @param accounts The set of accounts to count the relationships (pairwise)
+	 *                 between.
+	 * @param filter   The filter that relationships must pass to be included in
+	 *                 the count.
+	 *
+	 * @return The number of relationships (that pass the filter) between each
+	 *         pair of accounts, organized in a map where the key is an
+	 *         unordered pair of account ids, and the value is the number of
+	 *         relationships.
+	 *
+	 * @throws TskCoreException if there is a problem querying the DB.
+	 */
+	public Map<AccountPair, Long> getRelationshipCountsPairwise(Set<AccountDeviceInstance> accounts, CommunicationsFilter filter) throws TskCoreException {
+
+		Set<Long> accountIDs = new HashSet<Long>();
+		Set<String> accountDeviceIDs = new HashSet<String>();
+		for (AccountDeviceInstance adi : accounts) {
+			accountIDs.add(adi.getAccount().getAccountID());
+			accountDeviceIDs.add("'"+adi.getDeviceId()+"'");
+		}
+		//set up applicable filters 
+		Set<String> applicableFilters = new HashSet<String>(Arrays.asList(
+				CommunicationsFilter.DateRangeFilter.class.getName(),
+				CommunicationsFilter.DeviceFilter.class.getName(),
+				CommunicationsFilter.RelationshipTypeFilter.class.getName()
+		));
+
+		String accountIDsCSL = StringUtils.buildCSVString(accountIDs);
+		String accountDeviceIDsCSL = StringUtils.buildCSVString(accountDeviceIDs);
+		String filterSQL = getCommunicationsFilterSQL(filter, applicableFilters);
+
+		final String queryString
+				= " SELECT  count(DISTINCT relationships.relationship_source_obj_id) AS count," //realtionship count
+				+ "		data_source_info.device_id AS device_id,"
+				//account 1 info
+				+ "		accounts1.account_id AS account1_id,"
+				+ "		accounts1.account_unique_identifier AS account1_unique_identifier,"
+				+ "		account_types1.type_name AS type_name1,"
+				+ "		account_types1.display_name AS display_name1,"
+				//account 2 info
+				+ "		accounts2.account_id AS account2_id,"
+				+ "		accounts2.account_unique_identifier AS account2_unique_identifier,"
+				+ "		account_types2.type_name AS type_name2,"
+				+ "		account_types2.display_name AS display_name2"
+				+ " FROM account_relationships AS relationships"
+				+ "	JOIN data_source_info AS data_source_info"
+				+ "		ON relationships.data_source_obj_id = data_source_info.obj_id "
+				//account1 aliases
+				+ "	JOIN accounts AS accounts1	 "
+				+ "		ON accounts1.account_id = relationships.account1_id"
+				+ "	JOIN account_types AS account_types1"
+				+ "		ON accounts1.account_type_id = account_types1.account_type_id"
+				//account2 aliases
+				+ "	JOIN accounts AS accounts2	 "
+				+ "		ON accounts2.account_id = relationships.account2_id"
+				+ "	JOIN account_types AS account_types2"
+				+ "		ON accounts2.account_type_id = account_types2.account_type_id"
+				+ " WHERE (( relationships.account1_id IN (" + accountIDsCSL + ")) "
+				+ "		AND ( relationships.account2_id IN ( " + accountIDsCSL + " ))"
+				+ "		AND ( data_source_info.device_id IN (" + accountDeviceIDsCSL + "))) "
+				+ (filterSQL.isEmpty() ? "" : " AND " + filterSQL)
+				+ "  GROUP BY data_source_info.device_id, "
+				+ "		accounts1.account_id, "
+				+ "		account_types1.type_name, "
+				+ "		account_types1.display_name, "
+				+ "		accounts2.account_id, "
+				+ "		account_types2.type_name, "
+				+ "		account_types2.display_name";
+		CaseDbConnection connection = db.getConnection();
+		db.acquireSingleUserCaseReadLock();
+		Statement s = null;
+		ResultSet rs = null;
+
+		Map<AccountPair, Long> results = new HashMap<AccountPair, Long>();
+
+		try {
+			s = connection.createStatement();
+			rs = connection.executeQuery(s, queryString); //NON-NLS
+
+			while (rs.next()) {
+				//make account 1
+				Account.Type type1 = new Account.Type(rs.getString("type_name1"), rs.getString("display_name1"));
+				AccountDeviceInstance adi1 = new AccountDeviceInstance(new Account(rs.getLong("account1_id"), type1,
+						rs.getString("account1_unique_identifier")),
+						rs.getString("device_id"));
+
+				//make account 2
+				Account.Type type2 = new Account.Type(rs.getString("type_name2"), rs.getString("display_name2"));
+				AccountDeviceInstance adi2 = new AccountDeviceInstance(new Account(rs.getLong("account2_id"), type2,
+						rs.getString("account2_unique_identifier")),
+						rs.getString("device_id"));
+
+				AccountPair relationshipKey = new AccountPair(adi1, adi2);
+				long count = rs.getLong("count");
+
+				//merge counts for relationships that have the accounts flipped.
+				Long oldCount = results.get(relationshipKey);
+				if (oldCount != null) {
+					count += oldCount;
+				}
+				results.put(relationshipKey, count);
+			}
+			return results;
+		} catch (SQLException ex) {
+			throw new TskCoreException("Error getting relationships between accounts. " + ex.getMessage(), ex);
+		} finally {
+			closeResultSet(rs);
+			closeStatement(s);
+			connection.close();
+			db.releaseSingleUserCaseReadLock();
+		}
+	}
+
+	/**
+	 * Get the number of relationship sources for the relationships between
+	 * account1 and account2, that pass the given filter.
+	 *
+	 * @param account1 The first account.
+	 * @param account2 The second account.
+	 * @param filter   The filter that relationships must pass to be counted.
+	 *
+	 * @return The number of relationship sources for the relationships between
+	 *         account1 and account2, that pass the given filter.
+	 *
+	 * @throws TskCoreException
+	 */
+	public long getRelationshipSourcesCount(AccountDeviceInstance account1, AccountDeviceInstance account2, CommunicationsFilter filter) throws TskCoreException {
+
+		//set up applicable filters 
+		Set<String> applicableFilters = new HashSet<String>(Arrays.asList(
+				CommunicationsFilter.DateRangeFilter.class.getName(),
+				CommunicationsFilter.DeviceFilter.class.getName(),
+				CommunicationsFilter.RelationshipTypeFilter.class.getName()
+		));
+		String filterSQL = getCommunicationsFilterSQL(filter, applicableFilters);
+		final String queryString = "SELECT count(distinct artifact_id) as count "
+				+ " FROM blackboard_artifacts AS artifacts"
+				+ "	JOIN account_relationships AS relationships"
+				+ "		ON artifacts.artifact_obj_id = relationships.relationship_source_obj_id"
+				+ " WHERE (( relationships.account1_id = " + account1.getAccount().getAccountID()
+				+ " AND relationships.account2_id  = " + account2.getAccount().getAccountID()
+				+ " ) OR ( relationships.account2_id = " + account1.getAccount().getAccountID()
+				+ "			AND relationships.account1_id =" + account2.getAccount().getAccountID() + " ))"
+				+ (filterSQL.isEmpty() ? "" : " AND " + filterSQL)
+				+ " ";
+		CaseDbConnection connection = db.getConnection();
+		db.acquireSingleUserCaseReadLock();
+		Statement s = null;
+		ResultSet rs = null;
+
+		try {
+			s = connection.createStatement();
+			rs = connection.executeQuery(s, queryString); //NON-NLS
+
+			while (rs.next()) {
+				return rs.getLong("count");
+			}
+
+		} catch (SQLException ex) {
+			throw new TskCoreException("Error getting relationships between accounts. " + ex.getMessage(), ex);
+		} finally {
+			closeResultSet(rs);
+			closeStatement(s);
+			connection.close();
+			db.releaseSingleUserCaseReadLock();
+		}
+		return 0;
+	}
+
+	/**
+	 * Get the number of unique relationship sources (such as EMAIL artifacts)
+	 * associated with an account on a given device (AccountDeviceInstance) that
+	 * meet the filter criteria.
+	 *
 	 * Applicable filters: RelationshipTypeFilter, DateRangeFilter
 	 *
 	 * @param accountDeviceInstance Account of interest
@@ -856,8 +1028,9 @@ public long getRelationshipSourcesCount(AccountDeviceInstance accountDeviceInsta
 	}
 
 	/**
-     * Get the unique relationship sources (such as EMAIL artifacts) associated with an
-     * account on a given device (AccountDeviceInstance) that meet the filter criteria.
+	 * Get the unique relationship sources (such as EMAIL artifacts) associated
+	 * with an account on a given device (AccountDeviceInstance) that meet the
+	 * filter criteria.
 	 *
 	 * Applicable filters: RelationshipTypeFilter, DateRangeFilter
 	 *
@@ -865,7 +1038,7 @@ public long getRelationshipSourcesCount(AccountDeviceInstance accountDeviceInsta
 	 *                                  which to get the relationship sources.
 	 * @param filter                    Filters to apply.
 	 *
-	 * @return number of relationship sources found for given account(s).
+	 * @return relationship sources found for given account(s).
 	 *
 	 * @throws org.sleuthkit.datamodel.TskCoreException
 	 */
@@ -903,8 +1076,10 @@ public Set<Content> getRelationshipSources(Set<AccountDeviceInstance> accountDev
 
 		// set up applicable filters
 		Set<String> applicableFilters = new HashSet<String>(Arrays.asList(
-				CommunicationsFilter.RelationshipTypeFilter.class.getName(),
-				CommunicationsFilter.DateRangeFilter.class.getName()
+				CommunicationsFilter.RelationshipTypeFilter.class
+						.getName(),
+				CommunicationsFilter.DateRangeFilter.class
+						.getName()
 		));
 		String filterSQL = getCommunicationsFilterSQL(filter, applicableFilters);
 
@@ -952,6 +1127,185 @@ public Set<Content> getRelationshipSources(Set<AccountDeviceInstance> accountDev
 		}
 	}
 
+	/**
+	 * Get a set of AccountDeviceInstances that have relationships with the
+	 * given AccountDeviceInstance and meet the criteria of the given filter.
+	 *
+	 * @param accountDeviceInstance The account device instance.
+	 * @param filter                The filters to apply.
+	 *
+	 * @return A set of AccountDeviceInstances that have relationships with the
+	 *         given AccountDeviceInstance and meet the criteria of the given
+	 *         filter.
+	 *
+	 * @throws TskCoreException if there is a serious error executing he query.
+	 */
+	public List<AccountDeviceInstance> getRelatedAccountDeviceInstances(AccountDeviceInstance accountDeviceInstance, CommunicationsFilter filter) throws TskCoreException {
+		final List<Long> dataSourceObjIds
+				= getSleuthkitCase().getDataSourceObjIds(accountDeviceInstance.getDeviceId());
+
+		//set up applicable filters 
+		Set<String> applicableInnerQueryFilters = new HashSet<String>(Arrays.asList(
+				CommunicationsFilter.DateRangeFilter.class
+						.getName(),
+				CommunicationsFilter.DeviceFilter.class
+						.getName(),
+				CommunicationsFilter.RelationshipTypeFilter.class
+						.getName()
+		));
+
+		String innerQueryfilterSQL = getCommunicationsFilterSQL(filter, applicableInnerQueryFilters);
+
+		String innerQueryTemplate
+				= " SELECT %1$1s as account_id,"
+				+ "		  data_source_obj_id"
+				+ " FROM account_relationships as relationships"
+				+ " WHERE %2$1s = " + accountDeviceInstance.getAccount().getAccountID() + ""
+				+ " AND data_source_obj_id IN (" + StringUtils.buildCSVString(dataSourceObjIds) + ")"
+				+ (innerQueryfilterSQL.isEmpty() ? "" : " AND " + innerQueryfilterSQL);
+
+		String innerQuery1 = String.format(innerQueryTemplate, "account1_id", "account2_id");
+		String innerQuery2 = String.format(innerQueryTemplate, "account2_id", "account1_id");
+
+		//this query groups by account_id and data_source_obj_id across both innerQueries
+		String combinedInnerQuery
+				= "SELECT account_id, data_source_obj_id "
+				+ " FROM ( " + innerQuery1 + " UNION " + innerQuery2 + " ) AS  inner_union"
+				+ " GROUP BY account_id, data_source_obj_id";
+
+		// set up applicable filters
+		Set<String> applicableFilters = new HashSet<String>(Arrays.asList(
+				CommunicationsFilter.AccountTypeFilter.class
+						.getName()
+		));
+
+		String filterSQL = getCommunicationsFilterSQL(filter, applicableFilters);
+
+		String queryStr
+				= //account info
+				" accounts.account_id AS account_id,"
+				+ " accounts.account_unique_identifier AS account_unique_identifier,"
+				//account type info
+				+ " account_types.type_name AS type_name,"
+				//Account device instance info
+				+ " data_source_info.device_id AS device_id"
+				+ " FROM ( " + combinedInnerQuery + " ) AS account_device_instances"
+				+ " JOIN accounts AS accounts"
+				+ "		ON accounts.account_id = account_device_instances.account_id"
+				+ " JOIN account_types AS account_types"
+				+ "		ON accounts.account_type_id = account_types.account_type_id"
+				+ " JOIN data_source_info AS data_source_info"
+				+ "		ON account_device_instances.data_source_obj_id = data_source_info.obj_id"
+				+ (filterSQL.isEmpty() ? "" : " WHERE " + filterSQL);
+
+		switch (db.getDatabaseType()) {
+			case POSTGRESQL:
+				queryStr = "SELECT DISTINCT ON ( accounts.account_id, data_source_info.device_id) " + queryStr;
+				break;
+			case SQLITE:
+				queryStr = "SELECT " + queryStr + " GROUP BY accounts.account_id, data_source_info.device_id";
+				break;
+			default:
+				throw new TskCoreException("Unknown DB Type: " + db.getDatabaseType().name());
+		}
+
+		CaseDbConnection connection = db.getConnection();
+		db.acquireSingleUserCaseReadLock();
+		Statement s = null;
+		ResultSet rs = null;
+
+		try {
+			s = connection.createStatement();
+
+			rs = connection.executeQuery(s, queryStr); //NON-NLS
+			ArrayList<AccountDeviceInstance> accountDeviceInstances = new ArrayList<AccountDeviceInstance>();
+			while (rs.next()) {
+				long account_id = rs.getLong("account_id");
+				String deviceID = rs.getString("device_id");
+				final String type_name = rs.getString("type_name");
+				final String account_unique_identifier = rs.getString("account_unique_identifier");
+
+				Account.Type accountType = typeNameToAccountTypeMap.get(type_name);
+				Account account = new Account(account_id, accountType, account_unique_identifier);
+				accountDeviceInstances.add(new AccountDeviceInstance(account, deviceID));
+			}
+
+			return accountDeviceInstances;
+		} catch (SQLException ex) {
+			throw new TskCoreException("Error getting account device instances. " + ex.getMessage(), ex);
+		} finally {
+			closeResultSet(rs);
+			closeStatement(s);
+			connection.close();
+			db.releaseSingleUserCaseReadLock();
+		}
+	}
+
+	/**
+	 * Get the relationship sources of relationships between the given account
+	 * device instances.
+	 *
+	 * Applicable filters: RelationshipTypeFilter, DateRangeFilter
+	 *
+	 * @param account1 First AccountDeviceInstance
+	 * @param account2 Second AccountDeviceInstance
+	 * @param filter   Filters to apply.
+	 *
+	 * @return relationship sources for relationships between account1 and
+	 *         account2.
+	 *
+	 * @throws org.sleuthkit.datamodel.TskCoreException
+	 */
+	public List<Content> getRelationshipSources(AccountDeviceInstance account1, AccountDeviceInstance account2, CommunicationsFilter filter) throws TskCoreException {
+
+		//set up applicable filters 
+		Set<String> applicableFilters = new HashSet<String>(Arrays.asList(
+				CommunicationsFilter.DateRangeFilter.class.getName(),
+				CommunicationsFilter.DeviceFilter.class.getName(),
+				CommunicationsFilter.RelationshipTypeFilter.class.getName()
+		));
+		String filterSQL = getCommunicationsFilterSQL(filter, applicableFilters);
+		final String queryString = "SELECT artifacts.artifact_id AS artifact_id,"
+				+ "		artifacts.obj_id AS obj_id,"
+				+ "		artifacts.artifact_obj_id AS artifact_obj_id,"
+				+ "		artifacts.data_source_obj_id AS data_source_obj_id,"
+				+ "		artifacts.artifact_type_id AS artifact_type_id,"
+				+ "		artifacts.review_status_id AS review_status_id"
+				+ " FROM blackboard_artifacts AS artifacts"
+				+ "	JOIN account_relationships AS relationships"
+				+ "		ON artifacts.artifact_obj_id = relationships.relationship_source_obj_id"
+				+ " WHERE (( relationships.account1_id = " + account1.getAccount().getAccountID()
+				+ " AND relationships.account2_id  = " + account2.getAccount().getAccountID()
+				+ " ) OR (	  relationships.account2_id = " + account1.getAccount().getAccountID()
+				+ " AND relationships.account1_id =" + account2.getAccount().getAccountID() + " ))"
+				+ (filterSQL.isEmpty() ? "" : " AND " + filterSQL);
+		CaseDbConnection connection = db.getConnection();
+		db.acquireSingleUserCaseReadLock();
+		Statement s = null;
+		ResultSet rs = null;
+		try {
+			s = connection.createStatement();
+			rs = connection.executeQuery(s, queryString); //NON-NLS
+
+			ArrayList<Content> artifacts = new ArrayList<Content>();
+			while (rs.next()) {
+				BlackboardArtifact.Type bbartType = db.getArtifactType(rs.getInt("artifact_type_id"));
+				artifacts.add(new BlackboardArtifact(db, rs.getLong("artifact_id"), rs.getLong("obj_id"), rs.getLong("artifact_obj_id"), rs.getLong("data_source_obj_id"),
+						bbartType.getTypeID(), bbartType.getTypeName(), bbartType.getDisplayName(),
+						BlackboardArtifact.ReviewStatus.withID(rs.getInt("review_status_id"))));
+			}
+
+			return artifacts;
+		} catch (SQLException ex) {
+			throw new TskCoreException("Error getting relationships between accounts. " + ex.getMessage(), ex);
+		} finally {
+			closeResultSet(rs);
+			closeStatement(s);
+			connection.close();
+			db.releaseSingleUserCaseReadLock();
+		}
+	}
+
 	/**
 	 * Get account_type_if for the given account type.
 	 *
@@ -974,18 +1328,6 @@ int getAccountTypeId(Account.Type accountType) {
 	 *
 	 * @return Set<UnorderedPair<Long>>
 	 */
-	private Set<UnorderedAccountPair> listToUnorderedPairs(List<Long> account_ids) {
-		Set<UnorderedAccountPair> relationships = new HashSet<UnorderedAccountPair>();
-
-		for (int i = 0; i < account_ids.size(); i++) {
-			for (int j = i + 1; j < account_ids.size(); j++) {
-				relationships.add(new UnorderedAccountPair(account_ids.get(i), account_ids.get(j)));
-			}
-		}
-
-		return relationships;
-	}
-
 	private String normalizeAccountID(Account.Type accountType, String accountUniqueID) {
 		String normailzeAccountID = accountUniqueID;
 
@@ -1056,49 +1398,9 @@ private String getCommunicationsFilterSQL(CommunicationsFilter commFilter, Set<S
 			sqlStr = "( " + sqlSB.toString() + " )";
 		}
 		return sqlStr;
-	}
-
-	/**
-	 * Class representing an unordered pair of account ids. <a,b> is same as
-	 * <b,a>
-	 */
-	private final class UnorderedAccountPair {
-
-		private final long account1_id;
-		private final long account2_id;
-
-		UnorderedAccountPair(long account1_id, long account2_id) {
-			this.account1_id = account1_id;
-			this.account2_id = account2_id;
-		}
 
-		@Override
-		public int hashCode() {
-			return new Long(account1_id).hashCode() + new Long(account2_id).hashCode();
-		}
-
-		@Override
-		public boolean equals(Object other) {
-			if (other == this) {
-				return true;
-			}
-			if (!(other instanceof UnorderedAccountPair)) {
-				return false;
-			}
-
-			UnorderedAccountPair otherPair = (UnorderedAccountPair) other;
-			return ((account1_id == otherPair.account1_id && account2_id == otherPair.account2_id)
-					|| (account1_id == otherPair.account2_id && account2_id == otherPair.account1_id));
-		}
-
-		public long getFirst() {
-			return account1_id;
-		}
-
-		public long getSecond() {
-			return account2_id;
-		}
 	}
+}
 
 //	 /**
 //	 * Get all account instances of a given type
@@ -1491,54 +1793,6 @@ public long getSecond() {
 //		}
 //	}
 //	/**
-//	 * Returns relationships between two accounts
-//	 *
-//	 * @param account1_id account_id for account1
-//	 * @param account2_id account_id for account2
-//	 *
-//	 * @throws TskCoreException exception thrown if a critical error occurs
-//	 *                          within TSK core
-//	 */
-//	public List<BlackboardArtifact> getRelationships(long account1_id, long account2_id) throws TskCoreException {
-//		CaseDbConnection connection = db.getConnection();
-//		db.acquireSingleUserCaseReadLock();
-//		Statement s = null;
-//		ResultSet rs = null;
-//
-//		try {
-//			s = connection.createStatement();
-//			rs = connection.executeQuery(s, "SELECT artifacts.artifact_id AS artifact_id,"
-//					+ " artifacts.obj_id AS obj_id,"
-//					+ " artifacts.artifact_obj_id AS artifact_obj_id,"
-//					+ " artifacts.data_source_obj_id AS data_source_obj_id,"
-//					+ " artifacts.artifact_type_id AS artifact_type_id,"
-//					+ " artifacts.review_status_id AS review_status_id"
-//					+ " FROM blackboard_artifacts AS artifacts"
-//					+ "	JOIN relationships AS relationships"
-//					+ "		ON artifacts.artifact_obj_id = relationships.relationship_source_obj_id"
-//					+ " WHERE relationships.account1_id IN ( " + account1_id + ", " + account2_id + " )"
-//					+ " AND relationships.account2_id IN ( " + account1_id + ", " + account2_id + " )"
-//			); //NON-NLS
-//
-//			ArrayList<BlackboardArtifact> artifacts = new ArrayList<BlackboardArtifact>();
-//			while (rs.next()) {
-//				BlackboardArtifact.Type bbartType = db.getArtifactType(rs.getInt("artifact_type_id"));
-//				artifacts.add(new BlackboardArtifact(db, rs.getLong("artifact_id"), rs.getLong("obj_id"), rs.getLong("artifact_obj_id"), rs.getLong("data_source_obj_id"),
-//						bbartType.getTypeID(), bbartType.getTypeName(), bbartType.getDisplayName(),
-//						BlackboardArtifact.ReviewStatus.withID(rs.getInt("review_status_id"))));
-//			}
-//
-//			return artifacts;
-//		} catch (SQLException ex) {
-//			throw new TskCoreException("Error getting relationships bteween accounts. " + ex.getMessage(), ex);
-//		} finally {
-//			closeResultSet(rs);
-//			closeStatement(s);
-//			connection.close();
-//			db.releaseSingleUserCaseReadLock();
-//		}
-//	}
-//	/**
 //	 * Returns relationships, of given type, between two accounts
 //	 *
 //	 * @param account1_id  account1 artifact ID
@@ -1661,4 +1915,3 @@ public long getSecond() {
 //			db.releaseSingleUserCaseWriteLock();
 //		}
 //	}
-}
diff --git a/bindings/java/src/org/sleuthkit/datamodel/Relationship.java b/bindings/java/src/org/sleuthkit/datamodel/Relationship.java
index 68360ffbb24c9c245d994bbb927912d2d9507418..17e9cc6a7e97994e0d069e9008f5ab7d5b0383be 100644
--- a/bindings/java/src/org/sleuthkit/datamodel/Relationship.java
+++ b/bindings/java/src/org/sleuthkit/datamodel/Relationship.java
@@ -1,7 +1,7 @@
 /*
  * SleuthKit Java Bindings
  *
- * Copyright 2017 Basis Technology Corp.
+ * Copyright 2017-18 Basis Technology Corp.
  * Contact: carrier <at> sleuthkit <dot> org
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -36,6 +36,7 @@
  */
 public class Relationship {
 
+
 	public static final class Type {
 
 		private final String displayName;
diff --git a/bindings/java/test/org/sleuthkit/datamodel/CommunicationsManagerTest.java b/bindings/java/test/org/sleuthkit/datamodel/CommunicationsManagerTest.java
index 4fc41b5df43f8b479b1b92f40c9fde2c4ed8165c..d2e025b95d3fd6a6b0cc8baf5afe6cf0874934b2 100644
--- a/bindings/java/test/org/sleuthkit/datamodel/CommunicationsManagerTest.java
+++ b/bindings/java/test/org/sleuthkit/datamodel/CommunicationsManagerTest.java
@@ -25,6 +25,7 @@
 import static java.util.Collections.singleton;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -226,6 +227,10 @@ public class CommunicationsManagerTest {
 	private static HashSet<AccountDeviceInstance> EMAILS_ABC_DS1;
 	private static HashSet<AccountDeviceInstance> PHONES_2345_DS1;
 	private static HashSet<AccountDeviceInstance> PHONES_12345_DS2;
+	private static AccountFileInstance deviceAccount_1;
+	private static AccountFileInstance deviceAccount_2;
+	private static AccountDeviceInstance ds1DeviceAccount;
+	private static AccountDeviceInstance ds2DeviceAccount;
 
 	public CommunicationsManagerTest() {
 	}
@@ -257,11 +262,12 @@ public static void setUpClass() {
 
 			// Create some commmunication artiacts from DS1
 			{
-				VirtualDirectory rootDirectory_1 = dataSource_1.getRootDirectory();
+				VirtualDirectory rootDirectory_1 = dataSource_1;
 				AbstractFile sourceContent_1 = rootDirectory_1;	// Let the root dorectory be the source for all artifacts
 
 				// Create a Device account for Device1
-				AccountFileInstance deviceAccount_1 = caseDB.getCommunicationsManager().createAccountFileInstance(Account.Type.DEVICE, DS1_DEVICEID, MODULE_NAME, rootDirectory_1);
+				deviceAccount_1 = caseDB.getCommunicationsManager().createAccountFileInstance(Account.Type.DEVICE, DS1_DEVICEID, MODULE_NAME, rootDirectory_1);
+				ds1DeviceAccount = new AccountDeviceInstance(deviceAccount_1.getAccount(), DS1_DEVICEID);
 
 				// Create some email message artifacts
 				BlackboardArtifact emailMsg = addEmailMsgArtifact(EMAIL_A, EMAIL_B, "", "",
@@ -305,11 +311,12 @@ public static void setUpClass() {
 
 			// Create some commmunication artiacts from DS2
 			{
-				VirtualDirectory rootDirectory_2 = dataSource_2.getRootDirectory();
+				VirtualDirectory rootDirectory_2 = dataSource_2;
 				AbstractFile sourceContent_2 = rootDirectory_2;	// Let the root directory be the source for all artifacts
 
 				// Create a Device accocunt for Device1
-				AccountFileInstance deviceAccount_2 = caseDB.getCommunicationsManager().createAccountFileInstance(Account.Type.DEVICE, DS2_DEVICEID, MODULE_NAME, sourceContent_2);
+				deviceAccount_2 = caseDB.getCommunicationsManager().createAccountFileInstance(Account.Type.DEVICE, DS2_DEVICEID, MODULE_NAME, sourceContent_2);
+				ds2DeviceAccount = new AccountDeviceInstance(deviceAccount_2.getAccount(), DS2_DEVICEID);
 
 				// Add some Call logs
 				addCalllogArtifact(deviceAccount_2, NAME_1, PHONENUM_1, 1483272732, 100, "Outgoing", sourceContent_2);
@@ -492,6 +499,105 @@ public void accountTypeFilterTests() throws TskCoreException {
 		}
 	}
 
+	@Test
+	public void getRelationshipsTests() throws TskCoreException {
+
+		System.out.println("CommsMgr API - getRelationships With Filters tests");
+
+		// Test  DS1: EMAIL A <-> EMAIL B, no filters
+		{
+
+			List<Content> accountDeviceInstances
+					= commsMgr.getRelationshipSources(EMAIL_A_DS1, EMAIL_B_DS1, null);
+			assertEquals(2, accountDeviceInstances.size());
+		}
+
+		// Test  DS1: EMAIL A <-> EMAIL c, no filters
+		{
+
+			List<Content> accountDeviceInstances
+					= commsMgr.getRelationshipSources(EMAIL_A_DS1, EMAIL_C_DS1, null);
+			assertEquals(2, accountDeviceInstances.size());
+		}
+
+		// Test  DS1: EMAIL B <-> EMAIL C, no filters
+		{
+
+			List<Content> accountDeviceInstances
+					= commsMgr.getRelationshipSources(EMAIL_B_DS1, EMAIL_C_DS1, null);
+			assertEquals(1, accountDeviceInstances.size());
+		}
+
+		// Test  DS1: EMAIL B <-> EMAIL C, contacts
+		{
+			CommunicationsFilter communicationsFilter = new CommunicationsFilter(Arrays.asList(
+					new RelationshipTypeFilter(singleton(Relationship.Type.CONTACT))));
+			List<Content> accountDeviceInstances
+					= commsMgr.getRelationshipSources(EMAIL_B_DS1, EMAIL_C_DS1, communicationsFilter);
+			assertEquals(0, accountDeviceInstances.size());
+		}
+	}
+
+	@Test
+	public void getRelatedAccountDeviceInstancesWithFilterTests() throws TskCoreException {
+
+		System.out.println("CommsMgr API - getRelatedAccountDeviceInstances With Filters tests");
+
+		// Test  DS1: EMAIL A, no filters
+		{
+			List<AccountDeviceInstance> accountDeviceInstances
+					= commsMgr.getRelatedAccountDeviceInstances(EMAIL_A_DS1, null);
+			assertEquals(2, accountDeviceInstances.size());
+
+		}
+
+		// Test  DS1: EMAIL A, filter on device and email accounts
+		{
+			CommunicationsFilter commsFilter = new CommunicationsFilter(Arrays.asList(
+					new DeviceFilter(singleton(DS1_DEVICEID)),
+					new AccountTypeFilter(singleton(EMAIL))
+			));
+			List<AccountDeviceInstance> accountDeviceInstances
+					= commsMgr.getRelatedAccountDeviceInstances(EMAIL_A_DS1, commsFilter);
+			assertEquals(2, accountDeviceInstances.size());
+
+		}
+
+		// Test  DS1: EMAIL A, filter - DS2 & EMAIL
+		{
+			CommunicationsFilter commsFilter = new CommunicationsFilter(Arrays.asList(
+					new DeviceFilter(singleton(DS2_DEVICEID)),
+					new AccountTypeFilter(singleton(EMAIL))
+			));
+			List<AccountDeviceInstance> accountDeviceInstances
+					= commsMgr.getRelatedAccountDeviceInstances(EMAIL_A_DS1, commsFilter);
+			assertEquals(0, accountDeviceInstances.size());
+
+		}
+
+		// Test DS1: Phone 2  , call logs
+		{
+			CommunicationsFilter commsFilter = new CommunicationsFilter(Arrays.asList(
+					new RelationshipTypeFilter(Arrays.asList(CALL_LOG))
+			));
+			List<AccountDeviceInstance> accountDeviceInstances
+					= commsMgr.getRelatedAccountDeviceInstances(PHONE_2_DS1, commsFilter);
+			System.out.println(accountDeviceInstances);
+			assertEquals(1, accountDeviceInstances.size());
+		}
+
+		// Test DS2: Phone 1  , msgs + call logs
+		{
+			CommunicationsFilter commsFilter = new CommunicationsFilter(Arrays.asList(
+					new RelationshipTypeFilter(Arrays.asList(CALL_LOG, MESSAGE))
+			));
+			List<AccountDeviceInstance> accountDeviceInstances
+					= commsMgr.getRelatedAccountDeviceInstances(PHONE_2_DS2, commsFilter);
+			System.out.println(accountDeviceInstances);
+			assertEquals(1, accountDeviceInstances.size());
+		}
+	}
+
 	@Test
 	public void getAccountDeviceInstanceWithFilterTests() throws TskCoreException {
 
@@ -1172,6 +1278,70 @@ public void communicationsWithFilterTests() throws TskCoreException {
 		}
 	}
 
+	@Test
+	public void getRelationshipCountsPairwiseTests() throws TskCoreException {
+		System.out.println("CommsMgr API - Get RelationshipCountsBetween test");
+
+		// Counts  for DS1 and PHONES 2,3,4,5, no Filter 
+		{
+			final Set<AccountDeviceInstance> accounts = new HashSet<AccountDeviceInstance>();
+			accounts.add(ds1DeviceAccount);
+			accounts.addAll(PHONES_2345_DS1);
+
+			Map<AccountPair, Long> counts = commsMgr.getRelationshipCountsPairwise(accounts, null);
+
+			assertEquals(3, counts.size());
+			assertEquals(Long.valueOf(5), counts.get(new AccountPair(ds1DeviceAccount, PHONE_2_DS1)));
+			assertEquals(Long.valueOf(2), counts.get(new AccountPair(ds1DeviceAccount, PHONE_3_DS1)));
+			assertEquals(Long.valueOf(1), counts.get(new AccountPair(ds1DeviceAccount, PHONE_5_DS1)));
+			assertNull(counts.get(new AccountPair(ds1DeviceAccount, PHONE_4_DS1)));
+
+		}
+
+		// Counts  for DS1 Email A and B and PHONES 2,3,4,5, no Filter 
+		{
+			final Set<AccountDeviceInstance> accounts = new HashSet<AccountDeviceInstance>();
+			accounts.add(ds1DeviceAccount);
+			accounts.add(EMAIL_A_DS1);
+			accounts.add(EMAIL_B_DS1);
+			accounts.addAll(PHONES_2345_DS1);
+
+			Map<AccountPair, Long> counts
+					= commsMgr.getRelationshipCountsPairwise(accounts, null);
+			assertEquals(4, counts.size());
+			assertEquals(Long.valueOf(5), counts.get(new AccountPair(ds1DeviceAccount, PHONE_2_DS1)));
+			assertEquals(Long.valueOf(2), counts.get(new AccountPair(ds1DeviceAccount, PHONE_3_DS1)));
+			assertEquals(Long.valueOf(1), counts.get(new AccountPair(ds1DeviceAccount, PHONE_5_DS1)));
+			assertEquals(Long.valueOf(2), counts.get(new AccountPair(EMAIL_A_DS1, EMAIL_B_DS1)));
+			assertNull(counts.get(new AccountPair(ds1DeviceAccount, PHONE_4_DS1)));
+		}
+
+		// Counts  for DS1, DS2,  PHONES 1, 2,3,4,5;  Filter on DS2 
+		{
+			final Set<AccountDeviceInstance> accounts = new HashSet<AccountDeviceInstance>();
+			accounts.add(ds1DeviceAccount);
+			accounts.add(ds2DeviceAccount);
+			accounts.addAll(PHONES_12345_DS2);
+
+			CommunicationsFilter commsFilter = new CommunicationsFilter(Arrays.asList(
+					new DeviceFilter(singleton(DS2_DEVICEID))
+			));
+			Map<AccountPair, Long> counts
+					= commsMgr.getRelationshipCountsPairwise(accounts, commsFilter);
+			assertEquals(4, counts.size());
+			assertNull(counts.get(new AccountPair(ds1DeviceAccount, PHONE_2_DS1)));
+			assertNull(counts.get(new AccountPair(ds1DeviceAccount, PHONE_3_DS1)));
+			assertNull(counts.get(new AccountPair(ds1DeviceAccount, PHONE_5_DS1)));
+			assertNull(counts.get(new AccountPair(EMAIL_A_DS1, EMAIL_B_DS1)));
+			assertNull(counts.get(new AccountPair(ds1DeviceAccount, PHONE_4_DS1)));
+
+			assertEquals(Long.valueOf(1), counts.get(new AccountPair(ds2DeviceAccount, PHONE_1_DS2)));
+			assertEquals(Long.valueOf(4), counts.get(new AccountPair(ds2DeviceAccount, PHONE_2_DS2)));
+			assertEquals(Long.valueOf(1), counts.get(new AccountPair(ds2DeviceAccount, PHONE_3_DS2)));
+			assertEquals(Long.valueOf(3), counts.get(new AccountPair(ds2DeviceAccount, PHONE_4_DS2)));
+		}
+	}
+
 
 	/*
 	 * Adds an Email msg artifact. Also creates Email AccountInstances, if