Loading packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +4 −3 Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ import com.android.systemui.dagger.qualifiers.RootView; import com.android.systemui.keyguard.DismissCallbackRegistry; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.ListenerSet; import java.io.PrintWriter; import java.util.ArrayList; Loading Loading @@ -83,11 +84,11 @@ public class KeyguardBouncer { private final Runnable mRemoveViewRunnable = this::removeView; private final KeyguardBypassController mKeyguardBypassController; private KeyguardHostViewController mKeyguardViewController; private final List<KeyguardResetCallback> mResetCallbacks = new ArrayList<>(); private final ListenerSet<KeyguardResetCallback> mResetCallbacks = new ListenerSet<>(); private final Runnable mResetRunnable = ()-> { if (mKeyguardViewController != null) { mKeyguardViewController.resetSecurityContainer(); for (KeyguardResetCallback callback : new ArrayList<>(mResetCallbacks)) { for (KeyguardResetCallback callback : mResetCallbacks) { callback.onKeyguardReset(); } } Loading Loading @@ -602,7 +603,7 @@ public class KeyguardBouncer { } public void addKeyguardResetCallback(KeyguardResetCallback callback) { mResetCallbacks.add(callback); mResetCallbacks.addIfAbsent(callback); } public void removeKeyguardResetCallback(KeyguardResetCallback callback) { Loading packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java +7 −8 Original line number Diff line number Diff line Loading @@ -38,11 +38,10 @@ import com.android.systemui.R; import com.android.systemui.statusbar.AlertingNotificationManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.util.ListenerSet; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashSet; /** * A manager which handles heads up notifications which is a special mode where Loading @@ -52,7 +51,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { private static final String TAG = "HeadsUpManager"; private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms"; protected final HashSet<OnHeadsUpChangedListener> mListeners = new HashSet<>(); protected final ListenerSet<OnHeadsUpChangedListener> mListeners = new ListenerSet<>(); protected final Context mContext; Loading Loading @@ -118,7 +117,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { * Adds an OnHeadUpChangedListener to observe events. */ public void addListener(@NonNull OnHeadsUpChangedListener listener) { mListeners.add(listener); mListeners.addIfAbsent(listener); } /** Loading Loading @@ -158,7 +157,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { NotificationPeekEvent.NOTIFICATION_PEEK, entry.getSbn().getUid(), entry.getSbn().getPackageName(), entry.getSbn().getInstanceId()); } for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) { for (OnHeadsUpChangedListener listener : mListeners) { if (isPinned) { listener.onHeadsUpPinned(entry); } else { Loading @@ -178,7 +177,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { entry.setHeadsUp(true); setEntryPinned((HeadsUpEntry) alertEntry, shouldHeadsUpBecomePinned(entry)); EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 1 /* visible */); for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) { for (OnHeadsUpChangedListener listener : mListeners) { listener.onHeadsUpStateChanged(entry, true); } } Loading @@ -189,7 +188,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { entry.setHeadsUp(false); setEntryPinned((HeadsUpEntry) alertEntry, false /* isPinned */); EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 0 /* visible */); for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) { for (OnHeadsUpChangedListener listener : mListeners) { listener.onHeadsUpStateChanged(entry, false); } } Loading @@ -207,7 +206,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { if (mHasPinnedNotification) { MetricsLogger.count(mContext, "note_peek", 1); } for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) { for (OnHeadsUpChangedListener listener : mListeners) { listener.onHeadsUpPinnedModeChanged(hasPinnedNotification); } } Loading packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt 0 → 100644 +47 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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 java.util.concurrent.CopyOnWriteArrayList /** * A collection of listeners, observers, callbacks, etc. * * This container is optimized for infrequent mutation and frequent iteration, with thread safety * and reentrant-safety guarantees as well. */ class ListenerSet<E> : Iterable<E> { private val listeners: CopyOnWriteArrayList<E> = CopyOnWriteArrayList() /** * A thread-safe, reentrant-safe method to add a listener. * Does nothing if the listener is already in the set. */ fun addIfAbsent(element: E): Boolean = listeners.addIfAbsent(element) /** * A thread-safe, reentrant-safe method to remove a listener. */ fun remove(element: E): Boolean = listeners.remove(element) /** * Returns an iterator over the listeners currently in the set. Note that to ensure * [ConcurrentModificationException] is never thrown, this iterator will not reflect changes * made to the set after the iterator is constructed. */ override fun iterator(): Iterator<E> = listeners.iterator() } packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt 0 → 100644 +135 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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 android.test.suitebuilder.annotation.SmallTest import androidx.test.runner.AndroidJUnit4 import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class ListenerSetTest : SysuiTestCase() { var runnableSet: ListenerSet<Runnable> = ListenerSet() @Before fun setup() { runnableSet = ListenerSet() } @Test fun addIfAbsent_doesNotDoubleAdd() { // setup & preconditions val runnable1 = Runnable { } val runnable2 = Runnable { } assertThat(runnableSet.toList()).isEmpty() // Test that an element can be added assertThat(runnableSet.addIfAbsent(runnable1)).isTrue() assertThat(runnableSet.toList()).containsExactly(runnable1) // Test that a second element can be added assertThat(runnableSet.addIfAbsent(runnable2)).isTrue() assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2) // Test that re-adding the first element does nothing and returns false assertThat(runnableSet.addIfAbsent(runnable1)).isFalse() assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2) } @Test fun remove_removesListener() { // setup and preconditions val runnable1 = Runnable { } val runnable2 = Runnable { } assertThat(runnableSet.toList()).isEmpty() runnableSet.addIfAbsent(runnable1) runnableSet.addIfAbsent(runnable2) assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2) // Test that removing the first runnable only removes that one runnable assertThat(runnableSet.remove(runnable1)).isTrue() assertThat(runnableSet.toList()).containsExactly(runnable2) // Test that removing a non-present runnable does not error assertThat(runnableSet.remove(runnable1)).isFalse() assertThat(runnableSet.toList()).containsExactly(runnable2) // Test that removing the other runnable succeeds assertThat(runnableSet.remove(runnable2)).isTrue() assertThat(runnableSet.toList()).isEmpty() } @Test fun remove_isReentrantSafe() { // 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 runnable2 = Runnable { runnablesCalled.add(2) } assertThat(runnableSet.toList()).isEmpty() runnableSet.addIfAbsent(runnable1) runnableSet.addIfAbsent(runnable2) assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2) // Test that both runnables are called and 1 was removed for (runnable in runnableSet) { runnable.run() } assertThat(runnablesCalled).containsExactly(1, 2) assertThat(runnableSet.toList()).containsExactly(runnable2) } @Test fun addIfAbsent_isReentrantSafe() { // Setup and preconditions val runnablesCalled = mutableListOf<Int>() 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) } assertThat(runnableSet.toList()).isEmpty() runnableSet.addIfAbsent(runnable1) runnableSet.addIfAbsent(runnable2) assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2) // Test that both original runnables are called and 99 was added but not called for (runnable in runnableSet) { runnable.run() } assertThat(runnablesCalled).containsExactly(1, 2) assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2, runnable99) } } No newline at end of file Loading
packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +4 −3 Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ import com.android.systemui.dagger.qualifiers.RootView; import com.android.systemui.keyguard.DismissCallbackRegistry; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.ListenerSet; import java.io.PrintWriter; import java.util.ArrayList; Loading Loading @@ -83,11 +84,11 @@ public class KeyguardBouncer { private final Runnable mRemoveViewRunnable = this::removeView; private final KeyguardBypassController mKeyguardBypassController; private KeyguardHostViewController mKeyguardViewController; private final List<KeyguardResetCallback> mResetCallbacks = new ArrayList<>(); private final ListenerSet<KeyguardResetCallback> mResetCallbacks = new ListenerSet<>(); private final Runnable mResetRunnable = ()-> { if (mKeyguardViewController != null) { mKeyguardViewController.resetSecurityContainer(); for (KeyguardResetCallback callback : new ArrayList<>(mResetCallbacks)) { for (KeyguardResetCallback callback : mResetCallbacks) { callback.onKeyguardReset(); } } Loading Loading @@ -602,7 +603,7 @@ public class KeyguardBouncer { } public void addKeyguardResetCallback(KeyguardResetCallback callback) { mResetCallbacks.add(callback); mResetCallbacks.addIfAbsent(callback); } public void removeKeyguardResetCallback(KeyguardResetCallback callback) { Loading
packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java +7 −8 Original line number Diff line number Diff line Loading @@ -38,11 +38,10 @@ import com.android.systemui.R; import com.android.systemui.statusbar.AlertingNotificationManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.util.ListenerSet; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashSet; /** * A manager which handles heads up notifications which is a special mode where Loading @@ -52,7 +51,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { private static final String TAG = "HeadsUpManager"; private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms"; protected final HashSet<OnHeadsUpChangedListener> mListeners = new HashSet<>(); protected final ListenerSet<OnHeadsUpChangedListener> mListeners = new ListenerSet<>(); protected final Context mContext; Loading Loading @@ -118,7 +117,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { * Adds an OnHeadUpChangedListener to observe events. */ public void addListener(@NonNull OnHeadsUpChangedListener listener) { mListeners.add(listener); mListeners.addIfAbsent(listener); } /** Loading Loading @@ -158,7 +157,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { NotificationPeekEvent.NOTIFICATION_PEEK, entry.getSbn().getUid(), entry.getSbn().getPackageName(), entry.getSbn().getInstanceId()); } for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) { for (OnHeadsUpChangedListener listener : mListeners) { if (isPinned) { listener.onHeadsUpPinned(entry); } else { Loading @@ -178,7 +177,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { entry.setHeadsUp(true); setEntryPinned((HeadsUpEntry) alertEntry, shouldHeadsUpBecomePinned(entry)); EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 1 /* visible */); for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) { for (OnHeadsUpChangedListener listener : mListeners) { listener.onHeadsUpStateChanged(entry, true); } } Loading @@ -189,7 +188,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { entry.setHeadsUp(false); setEntryPinned((HeadsUpEntry) alertEntry, false /* isPinned */); EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 0 /* visible */); for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) { for (OnHeadsUpChangedListener listener : mListeners) { listener.onHeadsUpStateChanged(entry, false); } } Loading @@ -207,7 +206,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { if (mHasPinnedNotification) { MetricsLogger.count(mContext, "note_peek", 1); } for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) { for (OnHeadsUpChangedListener listener : mListeners) { listener.onHeadsUpPinnedModeChanged(hasPinnedNotification); } } Loading
packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt 0 → 100644 +47 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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 java.util.concurrent.CopyOnWriteArrayList /** * A collection of listeners, observers, callbacks, etc. * * This container is optimized for infrequent mutation and frequent iteration, with thread safety * and reentrant-safety guarantees as well. */ class ListenerSet<E> : Iterable<E> { private val listeners: CopyOnWriteArrayList<E> = CopyOnWriteArrayList() /** * A thread-safe, reentrant-safe method to add a listener. * Does nothing if the listener is already in the set. */ fun addIfAbsent(element: E): Boolean = listeners.addIfAbsent(element) /** * A thread-safe, reentrant-safe method to remove a listener. */ fun remove(element: E): Boolean = listeners.remove(element) /** * Returns an iterator over the listeners currently in the set. Note that to ensure * [ConcurrentModificationException] is never thrown, this iterator will not reflect changes * made to the set after the iterator is constructed. */ override fun iterator(): Iterator<E> = listeners.iterator() }
packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt 0 → 100644 +135 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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 android.test.suitebuilder.annotation.SmallTest import androidx.test.runner.AndroidJUnit4 import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class ListenerSetTest : SysuiTestCase() { var runnableSet: ListenerSet<Runnable> = ListenerSet() @Before fun setup() { runnableSet = ListenerSet() } @Test fun addIfAbsent_doesNotDoubleAdd() { // setup & preconditions val runnable1 = Runnable { } val runnable2 = Runnable { } assertThat(runnableSet.toList()).isEmpty() // Test that an element can be added assertThat(runnableSet.addIfAbsent(runnable1)).isTrue() assertThat(runnableSet.toList()).containsExactly(runnable1) // Test that a second element can be added assertThat(runnableSet.addIfAbsent(runnable2)).isTrue() assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2) // Test that re-adding the first element does nothing and returns false assertThat(runnableSet.addIfAbsent(runnable1)).isFalse() assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2) } @Test fun remove_removesListener() { // setup and preconditions val runnable1 = Runnable { } val runnable2 = Runnable { } assertThat(runnableSet.toList()).isEmpty() runnableSet.addIfAbsent(runnable1) runnableSet.addIfAbsent(runnable2) assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2) // Test that removing the first runnable only removes that one runnable assertThat(runnableSet.remove(runnable1)).isTrue() assertThat(runnableSet.toList()).containsExactly(runnable2) // Test that removing a non-present runnable does not error assertThat(runnableSet.remove(runnable1)).isFalse() assertThat(runnableSet.toList()).containsExactly(runnable2) // Test that removing the other runnable succeeds assertThat(runnableSet.remove(runnable2)).isTrue() assertThat(runnableSet.toList()).isEmpty() } @Test fun remove_isReentrantSafe() { // 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 runnable2 = Runnable { runnablesCalled.add(2) } assertThat(runnableSet.toList()).isEmpty() runnableSet.addIfAbsent(runnable1) runnableSet.addIfAbsent(runnable2) assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2) // Test that both runnables are called and 1 was removed for (runnable in runnableSet) { runnable.run() } assertThat(runnablesCalled).containsExactly(1, 2) assertThat(runnableSet.toList()).containsExactly(runnable2) } @Test fun addIfAbsent_isReentrantSafe() { // Setup and preconditions val runnablesCalled = mutableListOf<Int>() 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) } assertThat(runnableSet.toList()).isEmpty() runnableSet.addIfAbsent(runnable1) runnableSet.addIfAbsent(runnable2) assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2) // Test that both original runnables are called and 99 was added but not called for (runnable in runnableSet) { runnable.run() } assertThat(runnablesCalled).containsExactly(1, 2) assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2, runnable99) } } No newline at end of file