Add ListenerSet and use in places which currently copy-on-iterate.
Test: atest ListenerSetTest Change-Id: Ic1320b4f6c424322451f7def11346865bf878f99
This commit is contained in:
@@ -44,6 +44,7 @@ import com.android.systemui.dagger.qualifiers.RootView;
|
|||||||
import com.android.systemui.keyguard.DismissCallbackRegistry;
|
import com.android.systemui.keyguard.DismissCallbackRegistry;
|
||||||
import com.android.systemui.shared.system.SysUiStatsLog;
|
import com.android.systemui.shared.system.SysUiStatsLog;
|
||||||
import com.android.systemui.statusbar.policy.KeyguardStateController;
|
import com.android.systemui.statusbar.policy.KeyguardStateController;
|
||||||
|
import com.android.systemui.util.ListenerSet;
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -83,11 +84,11 @@ public class KeyguardBouncer {
|
|||||||
private final Runnable mRemoveViewRunnable = this::removeView;
|
private final Runnable mRemoveViewRunnable = this::removeView;
|
||||||
private final KeyguardBypassController mKeyguardBypassController;
|
private final KeyguardBypassController mKeyguardBypassController;
|
||||||
private KeyguardHostViewController mKeyguardViewController;
|
private KeyguardHostViewController mKeyguardViewController;
|
||||||
private final List<KeyguardResetCallback> mResetCallbacks = new ArrayList<>();
|
private final ListenerSet<KeyguardResetCallback> mResetCallbacks = new ListenerSet<>();
|
||||||
private final Runnable mResetRunnable = ()-> {
|
private final Runnable mResetRunnable = ()-> {
|
||||||
if (mKeyguardViewController != null) {
|
if (mKeyguardViewController != null) {
|
||||||
mKeyguardViewController.resetSecurityContainer();
|
mKeyguardViewController.resetSecurityContainer();
|
||||||
for (KeyguardResetCallback callback : new ArrayList<>(mResetCallbacks)) {
|
for (KeyguardResetCallback callback : mResetCallbacks) {
|
||||||
callback.onKeyguardReset();
|
callback.onKeyguardReset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -602,7 +603,7 @@ public class KeyguardBouncer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addKeyguardResetCallback(KeyguardResetCallback callback) {
|
public void addKeyguardResetCallback(KeyguardResetCallback callback) {
|
||||||
mResetCallbacks.add(callback);
|
mResetCallbacks.addIfAbsent(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeKeyguardResetCallback(KeyguardResetCallback callback) {
|
public void removeKeyguardResetCallback(KeyguardResetCallback callback) {
|
||||||
|
|||||||
@@ -38,11 +38,10 @@ import com.android.systemui.R;
|
|||||||
import com.android.systemui.statusbar.AlertingNotificationManager;
|
import com.android.systemui.statusbar.AlertingNotificationManager;
|
||||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||||
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
|
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
|
||||||
|
import com.android.systemui.util.ListenerSet;
|
||||||
|
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.io.PrintWriter;
|
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
|
* A manager which handles heads up notifications which is a special mode where
|
||||||
@@ -52,7 +51,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
|
|||||||
private static final String TAG = "HeadsUpManager";
|
private static final String TAG = "HeadsUpManager";
|
||||||
private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms";
|
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;
|
protected final Context mContext;
|
||||||
|
|
||||||
@@ -118,7 +117,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
|
|||||||
* Adds an OnHeadUpChangedListener to observe events.
|
* Adds an OnHeadUpChangedListener to observe events.
|
||||||
*/
|
*/
|
||||||
public void addListener(@NonNull OnHeadsUpChangedListener listener) {
|
public void addListener(@NonNull OnHeadsUpChangedListener listener) {
|
||||||
mListeners.add(listener);
|
mListeners.addIfAbsent(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -158,7 +157,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
|
|||||||
NotificationPeekEvent.NOTIFICATION_PEEK, entry.getSbn().getUid(),
|
NotificationPeekEvent.NOTIFICATION_PEEK, entry.getSbn().getUid(),
|
||||||
entry.getSbn().getPackageName(), entry.getSbn().getInstanceId());
|
entry.getSbn().getPackageName(), entry.getSbn().getInstanceId());
|
||||||
}
|
}
|
||||||
for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) {
|
for (OnHeadsUpChangedListener listener : mListeners) {
|
||||||
if (isPinned) {
|
if (isPinned) {
|
||||||
listener.onHeadsUpPinned(entry);
|
listener.onHeadsUpPinned(entry);
|
||||||
} else {
|
} else {
|
||||||
@@ -178,7 +177,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
|
|||||||
entry.setHeadsUp(true);
|
entry.setHeadsUp(true);
|
||||||
setEntryPinned((HeadsUpEntry) alertEntry, shouldHeadsUpBecomePinned(entry));
|
setEntryPinned((HeadsUpEntry) alertEntry, shouldHeadsUpBecomePinned(entry));
|
||||||
EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 1 /* visible */);
|
EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 1 /* visible */);
|
||||||
for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) {
|
for (OnHeadsUpChangedListener listener : mListeners) {
|
||||||
listener.onHeadsUpStateChanged(entry, true);
|
listener.onHeadsUpStateChanged(entry, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -189,7 +188,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
|
|||||||
entry.setHeadsUp(false);
|
entry.setHeadsUp(false);
|
||||||
setEntryPinned((HeadsUpEntry) alertEntry, false /* isPinned */);
|
setEntryPinned((HeadsUpEntry) alertEntry, false /* isPinned */);
|
||||||
EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 0 /* visible */);
|
EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 0 /* visible */);
|
||||||
for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) {
|
for (OnHeadsUpChangedListener listener : mListeners) {
|
||||||
listener.onHeadsUpStateChanged(entry, false);
|
listener.onHeadsUpStateChanged(entry, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -207,7 +206,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
|
|||||||
if (mHasPinnedNotification) {
|
if (mHasPinnedNotification) {
|
||||||
MetricsLogger.count(mContext, "note_peek", 1);
|
MetricsLogger.count(mContext, "note_peek", 1);
|
||||||
}
|
}
|
||||||
for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) {
|
for (OnHeadsUpChangedListener listener : mListeners) {
|
||||||
listener.onHeadsUpPinnedModeChanged(hasPinnedNotification);
|
listener.onHeadsUpPinnedModeChanged(hasPinnedNotification);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* 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()
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
/*
|
||||||
|
* 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user