Add CopyOnLoopListenerSet which may be faster
Bug: 341479400
Test: atest SystemUITests
Flag: EXEMPT bugfix / unused utility
Change-Id: Ice43df54311aa1d045e1f007f92c65b95cf38196
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/CopyOnLoopListenerSetTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/CopyOnLoopListenerSetTest.kt
new file mode 100644
index 0000000..b08d799
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/CopyOnLoopListenerSetTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * 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 com.android.systemui.util
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CopyOnLoopListenerSetTest : ListenerSetTest() {
+ override fun makeRunnableListenerSet(): IListenerSet<Runnable> = CopyOnLoopListenerSet()
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/ListenerSetTest.kt
similarity index 89%
rename from packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/ListenerSetTest.kt
index 1404a4f..a4f031b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/ListenerSetTest.kt
@@ -34,8 +34,8 @@
@Test
fun addIfAbsent_doesNotDoubleAdd() {
// setup & preconditions
- val runnable1 = Runnable { }
- val runnable2 = Runnable { }
+ val runnable1 = Runnable {}
+ val runnable2 = Runnable {}
assertThat(runnableSet).isEmpty()
// Test that an element can be added
@@ -53,7 +53,7 @@
@Test
fun isEmpty_changes() {
- val runnable = Runnable { }
+ val runnable = Runnable {}
assertThat(runnableSet).isEmpty()
assertThat(runnableSet.isEmpty()).isTrue()
assertThat(runnableSet.isNotEmpty()).isFalse()
@@ -74,17 +74,17 @@
assertThat(runnableSet).isEmpty()
assertThat(runnableSet.size).isEqualTo(0)
- assertThat(runnableSet.addIfAbsent(Runnable { })).isTrue()
+ assertThat(runnableSet.addIfAbsent(Runnable {})).isTrue()
assertThat(runnableSet.size).isEqualTo(1)
- assertThat(runnableSet.addIfAbsent(Runnable { })).isTrue()
+ assertThat(runnableSet.addIfAbsent(Runnable {})).isTrue()
assertThat(runnableSet.size).isEqualTo(2)
}
@Test
fun contains_worksAsExpected() {
- val runnable1 = Runnable { }
- val runnable2 = Runnable { }
+ val runnable1 = Runnable {}
+ val runnable2 = Runnable {}
assertThat(runnableSet).isEmpty()
assertThat(runnable1 in runnableSet).isFalse()
assertThat(runnable2 in runnableSet).isFalse()
@@ -112,8 +112,8 @@
@Test
fun containsAll_worksAsExpected() {
- val runnable1 = Runnable { }
- val runnable2 = Runnable { }
+ val runnable1 = Runnable {}
+ val runnable2 = Runnable {}
assertThat(runnableSet).isEmpty()
assertThat(runnableSet.containsAll(listOf())).isTrue()
@@ -143,8 +143,8 @@
@Test
fun remove_removesListener() {
// setup and preconditions
- val runnable1 = Runnable { }
- val runnable2 = Runnable { }
+ val runnable1 = Runnable {}
+ val runnable2 = Runnable {}
assertThat(runnableSet).isEmpty()
runnableSet.addIfAbsent(runnable1)
runnableSet.addIfAbsent(runnable2)
@@ -168,15 +168,14 @@
// Setup and preconditions
val runnablesCalled = mutableListOf<Int>()
// runnable1 is configured to remove itself
- val runnable1 = object : Runnable {
- override fun run() {
- runnableSet.remove(this)
- runnablesCalled.add(1)
+ val runnable1 =
+ object : Runnable {
+ override fun run() {
+ runnableSet.remove(this)
+ runnablesCalled.add(1)
+ }
}
- }
- val runnable2 = Runnable {
- runnablesCalled.add(2)
- }
+ val runnable2 = Runnable { runnablesCalled.add(2) }
assertThat(runnableSet).isEmpty()
runnableSet.addIfAbsent(runnable1)
runnableSet.addIfAbsent(runnable2)
@@ -194,17 +193,13 @@
fun addIfAbsent_isReentrantSafe() {
// Setup and preconditions
val runnablesCalled = mutableListOf<Int>()
- val runnable99 = Runnable {
- runnablesCalled.add(99)
- }
+ val runnable99 = Runnable { runnablesCalled.add(99) }
// runnable1 is configured to add runnable99
val runnable1 = Runnable {
runnableSet.addIfAbsent(runnable99)
runnablesCalled.add(1)
}
- val runnable2 = Runnable {
- runnablesCalled.add(2)
- }
+ val runnable2 = Runnable { runnablesCalled.add(2) }
assertThat(runnableSet).isEmpty()
runnableSet.addIfAbsent(runnable1)
runnableSet.addIfAbsent(runnable2)
@@ -217,4 +212,4 @@
assertThat(runnablesCalled).containsExactly(1, 2)
assertThat(runnableSet).containsExactly(runnable1, runnable2, runnable99)
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/NamedListenerSetTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/NamedListenerSetTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/NamedListenerSetTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/NamedListenerSetTest.kt
diff --git a/packages/SystemUI/src/com/android/systemui/util/CopyOnLoopListenerSet.kt b/packages/SystemUI/src/com/android/systemui/util/CopyOnLoopListenerSet.kt
new file mode 100644
index 0000000..8a7ab80
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/CopyOnLoopListenerSet.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * 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 com.android.systemui.util
+
+/**
+ * A collection of listeners, observers, callbacks, etc.
+ *
+ * This container is optimized for frequent mutation and infrequent iteration, with reentrant-safety
+ * guarantees but without thread-safety guarantees. Specifically, to ensure that
+ * [ConcurrentModificationException] is not thrown when listeners mutate the set, this iterator will
+ * not reflect changes made to the set after the iterator is constructed.
+ */
+class CopyOnLoopListenerSet<E : Any>
+/** Private constructor takes the internal list so that we can use auto-delegation */
+private constructor(private val listeners: ArrayList<E>) :
+ Collection<E> by listeners, IListenerSet<E> {
+
+ /** Create a new instance */
+ constructor() : this(ArrayList())
+
+ @Suppress("UNCHECKED_CAST")
+ override fun iterator(): Iterator<E> = listeners.toArray().iterator() as Iterator<E>
+
+ override fun addIfAbsent(element: E): Boolean =
+ if (element in listeners) {
+ false
+ } else {
+ listeners.add(element)
+ }
+
+ override fun remove(element: E): Boolean = listeners.remove(element)
+}