diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/Multiset.java b/bramble-api/src/main/java/org/briarproject/bramble/api/Multiset.java new file mode 100644 index 0000000000000000000000000000000000000000..58db606b590583fa51ed63de59279524a874ad2f --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/Multiset.java @@ -0,0 +1,101 @@ +package org.briarproject.bramble.api; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +import javax.annotation.concurrent.NotThreadSafe; + +@NotThreadSafe +@NotNullByDefault +public class Multiset<T> { + + private final Map<T, Integer> map = new HashMap<>(); + + private int total = 0; + + /** + * Returns how many items the multiset contains in total. + */ + public int getTotal() { + return total; + } + + /** + * Returns how many unique items the multiset contains. + */ + public int getUnique() { + return map.size(); + } + + /** + * Returns how many of the given item the multiset contains. + */ + public int getCount(T t) { + Integer count = map.get(t); + return count == null ? 0 : count; + } + + /** + * Adds the given item to the multiset and returns how many of the item + * the multiset now contains. + */ + public int add(T t) { + Integer count = map.get(t); + if (count == null) count = 0; + map.put(t, count + 1); + total++; + return count + 1; + } + + /** + * Removes the given item from the multiset and returns how many of the + * item the multiset now contains. + * @throws NoSuchElementException if the item is not in the multiset. + */ + public int remove(T t) { + Integer count = map.get(t); + if (count == null) throw new NoSuchElementException(); + if (count == 1) map.remove(t); + else map.put(t, count - 1); + total--; + return count - 1; + } + + /** + * Removes all occurrences of the given item from the multiset. + */ + public int removeAll(T t) { + Integer count = map.remove(t); + if (count == null) return 0; + total -= count; + return count; + } + + /** + * Returns true if the multiset contains any occurrences of the given item. + */ + public boolean contains(T t) { + return map.containsKey(t); + } + + /** + * Removes all items from the multiset. + */ + public void clear() { + map.clear(); + total = 0; + } + + /** + * Returns the set of unique items the multiset contains. The returned set + * is unmodifiable. + */ + public Set<T> keySet() { + return Collections.unmodifiableSet(map.keySet()); + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/ConnectionRegistryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/ConnectionRegistryImpl.java index 780e46a6e3185646db974c6ee3982458d959188d..c12abae552fecc97a6e9fa21ad552159a37e39cb 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/ConnectionRegistryImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/ConnectionRegistryImpl.java @@ -1,5 +1,6 @@ package org.briarproject.bramble.plugin; +import org.briarproject.bramble.api.Multiset; import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; @@ -36,14 +37,14 @@ class ConnectionRegistryImpl implements ConnectionRegistry { private final Lock lock = new ReentrantLock(); // The following are locking: lock - private final Map<TransportId, Map<ContactId, Integer>> connections; - private final Map<ContactId, Integer> contactCounts; + private final Map<TransportId, Multiset<ContactId>> connections; + private final Multiset<ContactId> contactCounts; @Inject ConnectionRegistryImpl(EventBus eventBus) { this.eventBus = eventBus; connections = new HashMap<>(); - contactCounts = new HashMap<>(); + contactCounts = new Multiset<>(); } @Override @@ -56,21 +57,13 @@ class ConnectionRegistryImpl implements ConnectionRegistry { boolean firstConnection = false; lock.lock(); try { - Map<ContactId, Integer> m = connections.get(t); + Multiset<ContactId> m = connections.get(t); if (m == null) { - m = new HashMap<>(); + m = new Multiset<>(); connections.put(t, m); } - Integer count = m.get(c); - if (count == null) m.put(c, 1); - else m.put(c, count + 1); - count = contactCounts.get(c); - if (count == null) { - firstConnection = true; - contactCounts.put(c, 1); - } else { - contactCounts.put(c, count + 1); - } + m.add(c); + if (contactCounts.add(c) == 1) firstConnection = true; } finally { lock.unlock(); } @@ -91,23 +84,10 @@ class ConnectionRegistryImpl implements ConnectionRegistry { boolean lastConnection = false; lock.lock(); try { - Map<ContactId, Integer> m = connections.get(t); + Multiset<ContactId> m = connections.get(t); if (m == null) throw new IllegalArgumentException(); - Integer count = m.remove(c); - if (count == null) throw new IllegalArgumentException(); - if (count == 1) { - if (m.isEmpty()) connections.remove(t); - } else { - m.put(c, count - 1); - } - count = contactCounts.get(c); - if (count == null) throw new IllegalArgumentException(); - if (count == 1) { - lastConnection = true; - contactCounts.remove(c); - } else { - contactCounts.put(c, count - 1); - } + m.remove(c); + if (contactCounts.remove(c) == 0) lastConnection = true; } finally { lock.unlock(); } @@ -122,7 +102,7 @@ class ConnectionRegistryImpl implements ConnectionRegistry { public Collection<ContactId> getConnectedContacts(TransportId t) { lock.lock(); try { - Map<ContactId, Integer> m = connections.get(t); + Multiset<ContactId> m = connections.get(t); if (m == null) return Collections.emptyList(); List<ContactId> ids = new ArrayList<>(m.keySet()); if (LOG.isLoggable(INFO)) @@ -137,8 +117,8 @@ class ConnectionRegistryImpl implements ConnectionRegistry { public boolean isConnected(ContactId c, TransportId t) { lock.lock(); try { - Map<ContactId, Integer> m = connections.get(t); - return m != null && m.containsKey(c); + Multiset<ContactId> m = connections.get(t); + return m != null && m.contains(c); } finally { lock.unlock(); } @@ -148,7 +128,7 @@ class ConnectionRegistryImpl implements ConnectionRegistry { public boolean isConnected(ContactId c) { lock.lock(); try { - return contactCounts.containsKey(c); + return contactCounts.contains(c); } finally { lock.unlock(); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AndroidNotificationManagerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/AndroidNotificationManagerImpl.java index 5baa6830a9f24b742be522267b3dc1515631e51a..5c66a8d9caabae925382dbfd9d9c6fc71cf6d16f 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AndroidNotificationManagerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AndroidNotificationManagerImpl.java @@ -12,6 +12,7 @@ import android.support.annotation.StringRes; import android.support.annotation.UiThread; import android.support.v4.app.TaskStackBuilder; +import org.briarproject.bramble.api.Multiset; import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DbException; @@ -45,8 +46,7 @@ import org.briarproject.briar.api.privategroup.event.GroupMessageAddedEvent; import org.briarproject.briar.api.sharing.event.InvitationRequestReceivedEvent; import org.briarproject.briar.api.sharing.event.InvitationResponseReceivedEvent; -import java.util.HashMap; -import java.util.Map; +import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; @@ -109,11 +109,10 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager, private final AtomicBoolean used = new AtomicBoolean(false); // The following must only be accessed on the main UI thread - private final Map<ContactId, Integer> contactCounts = new HashMap<>(); - private final Map<GroupId, Integer> groupCounts = new HashMap<>(); - private final Map<GroupId, Integer> forumCounts = new HashMap<>(); - private final Map<GroupId, Integer> blogCounts = new HashMap<>(); - private int contactTotal = 0, groupTotal = 0, forumTotal = 0, blogTotal = 0; + private final Multiset<ContactId> contactCounts = new Multiset<>(); + private final Multiset<GroupId> groupCounts = new Multiset<>(); + private final Multiset<GroupId> forumCounts = new Multiset<>(); + private final Multiset<GroupId> blogCounts = new Multiset<>(); private int introductionTotal = 0; private int nextRequestId = 0; private ContactId blockedContact = null; @@ -197,28 +196,24 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager, @UiThread private void clearContactNotification() { contactCounts.clear(); - contactTotal = 0; notificationManager.cancel(PRIVATE_MESSAGE_NOTIFICATION_ID); } @UiThread private void clearGroupMessageNotification() { groupCounts.clear(); - groupTotal = 0; notificationManager.cancel(GROUP_MESSAGE_NOTIFICATION_ID); } @UiThread private void clearForumPostNotification() { forumCounts.clear(); - forumTotal = 0; notificationManager.cancel(FORUM_POST_NOTIFICATION_ID); } @UiThread private void clearBlogPostNotification() { blogCounts.clear(); - blogTotal = 0; notificationManager.cancel(BLOG_POST_NOTIFICATION_ID); } @@ -278,10 +273,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager, androidExecutor.runOnUiThread(() -> { if (blockContacts) return; if (c.equals(blockedContact)) return; - Integer count = contactCounts.get(c); - if (count == null) contactCounts.put(c, 1); - else contactCounts.put(c, count + 1); - contactTotal++; + contactCounts.add(c); updateContactNotification(true); }); } @@ -289,15 +281,14 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager, @Override public void clearContactNotification(ContactId c) { androidExecutor.runOnUiThread(() -> { - Integer count = contactCounts.remove(c); - if (count == null) return; // Already cleared - contactTotal -= count; - updateContactNotification(false); + if (contactCounts.removeAll(c) > 0) + updateContactNotification(false); }); } @UiThread private void updateContactNotification(boolean mayAlertAgain) { + int contactTotal = contactCounts.getTotal(); if (contactTotal == 0) { clearContactNotification(); } else if (settings.getBoolean(PREF_NOTIFY_PRIVATE, true)) { @@ -315,10 +306,11 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager, b.setLockscreenVisibility(CATEGORY_MESSAGE, showOnLockScreen); if (mayAlertAgain) setAlertProperties(b); setDeleteIntent(b, CONTACT_URI); - if (contactCounts.size() == 1) { + Set<ContactId> contacts = contactCounts.keySet(); + if (contacts.size() == 1) { // Touching the notification shows the relevant conversation Intent i = new Intent(appContext, ConversationActivity.class); - ContactId c = contactCounts.keySet().iterator().next(); + ContactId c = contacts.iterator().next(); i.putExtra(CONTACT_ID, c.getInt()); i.setData(Uri.parse(CONTACT_URI + "/" + c.getInt())); i.setFlags(FLAG_ACTIVITY_CLEAR_TOP); @@ -385,10 +377,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager, androidExecutor.runOnUiThread(() -> { if (blockGroups) return; if (g.equals(blockedGroup)) return; - Integer count = groupCounts.get(g); - if (count == null) groupCounts.put(g, 1); - else groupCounts.put(g, count + 1); - groupTotal++; + groupCounts.add(g); updateGroupMessageNotification(true); }); } @@ -396,15 +385,14 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager, @Override public void clearGroupMessageNotification(GroupId g) { androidExecutor.runOnUiThread(() -> { - Integer count = groupCounts.remove(g); - if (count == null) return; // Already cleared - groupTotal -= count; - updateGroupMessageNotification(false); + if (groupCounts.removeAll(g) > 0) + updateGroupMessageNotification(false); }); } @UiThread private void updateGroupMessageNotification(boolean mayAlertAgain) { + int groupTotal = groupCounts.getTotal(); if (groupTotal == 0) { clearGroupMessageNotification(); } else if (settings.getBoolean(PREF_NOTIFY_GROUP, true)) { @@ -422,10 +410,11 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager, b.setLockscreenVisibility(CATEGORY_SOCIAL, showOnLockScreen); if (mayAlertAgain) setAlertProperties(b); setDeleteIntent(b, GROUP_URI); - if (groupCounts.size() == 1) { + Set<GroupId> groups = groupCounts.keySet(); + if (groups.size() == 1) { // Touching the notification shows the relevant group Intent i = new Intent(appContext, GroupActivity.class); - GroupId g = groupCounts.keySet().iterator().next(); + GroupId g = groups.iterator().next(); i.putExtra(GROUP_ID, g.getBytes()); String idHex = StringUtils.toHexString(g.getBytes()); i.setData(Uri.parse(GROUP_URI + "/" + idHex)); @@ -461,10 +450,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager, androidExecutor.runOnUiThread(() -> { if (blockForums) return; if (g.equals(blockedGroup)) return; - Integer count = forumCounts.get(g); - if (count == null) forumCounts.put(g, 1); - else forumCounts.put(g, count + 1); - forumTotal++; + forumCounts.add(g); updateForumPostNotification(true); }); } @@ -472,15 +458,14 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager, @Override public void clearForumPostNotification(GroupId g) { androidExecutor.runOnUiThread(() -> { - Integer count = forumCounts.remove(g); - if (count == null) return; // Already cleared - forumTotal -= count; - updateForumPostNotification(false); + if (forumCounts.removeAll(g) > 0) + updateForumPostNotification(false); }); } @UiThread private void updateForumPostNotification(boolean mayAlertAgain) { + int forumTotal = forumCounts.getTotal(); if (forumTotal == 0) { clearForumPostNotification(); } else if (settings.getBoolean(PREF_NOTIFY_FORUM, true)) { @@ -498,10 +483,11 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager, b.setLockscreenVisibility(CATEGORY_SOCIAL, showOnLockScreen); if (mayAlertAgain) setAlertProperties(b); setDeleteIntent(b, FORUM_URI); - if (forumCounts.size() == 1) { + Set<GroupId> forums = forumCounts.keySet(); + if (forums.size() == 1) { // Touching the notification shows the relevant forum Intent i = new Intent(appContext, ForumActivity.class); - GroupId g = forumCounts.keySet().iterator().next(); + GroupId g = forums.iterator().next(); i.putExtra(GROUP_ID, g.getBytes()); String idHex = StringUtils.toHexString(g.getBytes()); i.setData(Uri.parse(FORUM_URI + "/" + idHex)); @@ -536,10 +522,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager, androidExecutor.runOnUiThread(() -> { if (blockBlogs) return; if (g.equals(blockedGroup)) return; - Integer count = blogCounts.get(g); - if (count == null) blogCounts.put(g, 1); - else blogCounts.put(g, count + 1); - blogTotal++; + blogCounts.add(g); updateBlogPostNotification(true); }); } @@ -547,15 +530,13 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager, @Override public void clearBlogPostNotification(GroupId g) { androidExecutor.runOnUiThread(() -> { - Integer count = blogCounts.remove(g); - if (count == null) return; // Already cleared - blogTotal -= count; - updateBlogPostNotification(false); + if (blogCounts.removeAll(g) > 0) updateBlogPostNotification(false); }); } @UiThread private void updateBlogPostNotification(boolean mayAlertAgain) { + int blogTotal = blogCounts.getTotal(); if (blogTotal == 0) { clearBlogPostNotification(); } else if (settings.getBoolean(PREF_NOTIFY_BLOG, true)) {